@windward/games 0.0.4 → 0.0.5

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 (27) 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/multipleChoice/MultipleChoice.vue +187 -127
  7. package/components/content/blocks/multipleChoice/QuestionDialog.vue +37 -13
  8. package/components/content/blocks/sevenStrikes/SevenStikes.vue +368 -0
  9. package/components/content/blocks/sevenStrikes/keyboard.vue +71 -0
  10. package/components/content/blocks/wordJumble/Jumble.vue +2 -10
  11. package/components/settings/CrosswordPuzzleSettingsManager.vue +12 -15
  12. package/components/settings/MultipleChoiceSettingsManager.vue +21 -7
  13. package/components/settings/SevenStrikesSettingsManager.vue +288 -0
  14. package/i18n/en-US/components/content/blocks/crossword.ts +18 -3
  15. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  16. package/i18n/en-US/components/content/blocks/multiple_choice.ts +2 -4
  17. package/i18n/en-US/components/content/blocks/seven_strikes.ts +6 -0
  18. package/i18n/en-US/components/settings/crossword.ts +2 -1
  19. package/i18n/en-US/components/settings/index.ts +2 -0
  20. package/i18n/en-US/components/settings/multiple_choice.ts +1 -1
  21. package/i18n/en-US/components/settings/seven_strikes.ts +8 -0
  22. package/i18n/en-US/shared/content_blocks.ts +1 -0
  23. package/i18n/en-US/shared/settings.ts +1 -0
  24. package/package.json +2 -1
  25. package/plugin.js +21 -0
  26. package/test/blocks/sevenStrikes/sevenStrikes.spec.js +24 -0
  27. package/test/settings/SevenStrikesManager.spec.js +53 -0
@@ -0,0 +1,368 @@
1
+ <template>
2
+ <div>
3
+ <v-col class="pa-0">
4
+ <h3>
5
+ {{
6
+ block.metadata.config.title
7
+ ? block.metadata.config.title
8
+ : $t(
9
+ 'plugin.games.components.content.blocks.seven_strikes.title'
10
+ )
11
+ }}
12
+ </h3>
13
+ <p>{{ block.metadata.config.instructions }}</p>
14
+ </v-col>
15
+ <v-col class="pa-0">
16
+ <template>
17
+ <v-carousel
18
+ v-model="carouselIndex"
19
+ @change="onSlideChanged($event)"
20
+ >
21
+ <v-carousel-item
22
+ v-for="(word, index) in block.metadata.config.words"
23
+ :key="index"
24
+ >
25
+ <v-row class="d-flex justify-center ma-2">
26
+ <p class="pa-3 mb-0 clueAndJumble">
27
+ {{ word.hint }}
28
+ </p>
29
+ </v-row>
30
+ <v-container
31
+ v-if="showFeedback"
32
+ :key="'feedback'"
33
+ :class="feedbackStatus"
34
+ class="mb-8"
35
+ >
36
+ <v-row class="d-flex" align="center">
37
+ <v-col cols="4"></v-col>
38
+ <v-col cols="4" class="d-flex justify-center">
39
+ <p class="">{{ feedback }}</p>
40
+ </v-col>
41
+ <v-col cols="4" class="d-flex justify-end">
42
+ <v-icon @click="onHideFeedback">
43
+ mdi-alpha-x-circle-outline</v-icon
44
+ >
45
+ </v-col>
46
+ </v-row>
47
+ </v-container>
48
+ <v-row class="justify-center">
49
+ <div
50
+ v-for="(
51
+ strike, strikeIndex
52
+ ) in sevenStrikesCounter"
53
+ :key="strikeIndex"
54
+ >
55
+ <div
56
+ v-if="strike.strike === false"
57
+ :ref="'strike' + strikeIndex"
58
+ class="strikeArea ml-1 mr-1 d-flex justify-center align-center"
59
+ maxlength="1"
60
+ ></div>
61
+ <div
62
+ class="strike"
63
+ v-if="strike.strike === true"
64
+ >
65
+ X
66
+ </div>
67
+ </div>
68
+ </v-row>
69
+ <v-row
70
+ class="justify-center mt-12"
71
+ v-if="word.splitWord"
72
+ >
73
+ <div
74
+ v-for="(letter, splitIndex) in word.splitWord"
75
+ :key="splitIndex"
76
+ >
77
+ <div
78
+ :ref="'input' + splitIndex"
79
+ class="textArea ml-1 mr-1"
80
+ maxlength="1"
81
+ >
82
+ <div v-if="letter.show === true">
83
+ {{ letter.letter }}
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </v-row>
88
+ <div>
89
+ <keyboard
90
+ v-if="!showFeedback"
91
+ :key="index"
92
+ :keyboardClass="keyBoardClass + index"
93
+ @onChange="onChange"
94
+ @onKeyPress="onKeyPress"
95
+ :input="input"
96
+ />
97
+ </div>
98
+ <v-row class="justify-center mt-12" v-if="showFeedback">
99
+ <v-btn
100
+ color="primary ml-4"
101
+ @click="onRevealAnswer"
102
+ >{{
103
+ $t(
104
+ 'plugin.games.components.content.blocks.seven_strikes.reveal'
105
+ )
106
+ }}</v-btn
107
+ >
108
+ <v-btn
109
+ color="primary ml-4"
110
+ @click="onSlideChanged(index)"
111
+ >{{
112
+ $t(
113
+ 'plugin.games.components.content.blocks.seven_strikes.again'
114
+ )
115
+ }}</v-btn
116
+ >
117
+ <v-btn
118
+ @click="onChangeSlide(index + 1)"
119
+ color="primary ml-4"
120
+ >{{
121
+ $t(
122
+ 'plugin.games.components.content.blocks.seven_strikes.next'
123
+ )
124
+ }}</v-btn
125
+ >
126
+ </v-row>
127
+ </v-carousel-item>
128
+ </v-carousel>
129
+ </template>
130
+ </v-col>
131
+ </div>
132
+ </template>
133
+
134
+ <script>
135
+ import _ from 'lodash'
136
+ import draggable from 'vuedraggable'
137
+ import keyboard from './keyboard.vue'
138
+ import CrudTable from '../../CrudTable'
139
+ import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
140
+
141
+ export default {
142
+ name: 'SevenStrikesGame',
143
+ components: { CrudTable, draggable, keyboard },
144
+ extends: BaseContentBlock,
145
+ beforeMount() {
146
+ if (_.isEmpty(this.block)) {
147
+ this.block = {}
148
+ }
149
+ if (_.isEmpty(this.block.metadata)) {
150
+ this.block.metadata = {}
151
+ }
152
+ if (_.isEmpty(this.block.metadata.config)) {
153
+ this.block.metadata.config = {}
154
+ }
155
+ if (_.isEmpty(this.block.metadata.config.words)) {
156
+ this.block.metadata.config.words = []
157
+ }
158
+ },
159
+ data() {
160
+ return {
161
+ feedback: '',
162
+ feedbackStatus: '',
163
+ showFeedback: false,
164
+ input: '',
165
+ sevenStrikesCounter: [
166
+ { strike: false },
167
+ { strike: false },
168
+ { strike: false },
169
+ { strike: false },
170
+ { strike: false },
171
+ { strike: false },
172
+ { strike: false },
173
+ ],
174
+ keyBoardClass: 'simple-keyboard',
175
+ onSlide: 0,
176
+ onStrike: 0,
177
+ carouselIndex: 0,
178
+ }
179
+ },
180
+ mounted() {},
181
+ methods: {
182
+ onChangeSlide(newIndex) {
183
+ if (newIndex >= this.block.metadata.config.words.length) {
184
+ newIndex = 0
185
+ }
186
+ this.carouselIndex = newIndex
187
+ this.onSlideChanged(newIndex)
188
+ },
189
+ onChange(input) {
190
+ this.input = input
191
+ },
192
+ onKeyPress(letter) {
193
+ // can't play when editing
194
+ if (this.render) {
195
+ // to lowercase
196
+ letter = letter.toLowerCase()
197
+ // check if word value contains input letter
198
+ const doesContain =
199
+ this.block.metadata.config.words[
200
+ this.onSlide
201
+ ].value.includes(letter)
202
+ if (doesContain) {
203
+ // loop over to show letters in case there are multiple of the same
204
+ this.block.metadata.config.words[
205
+ this.onSlide
206
+ ].splitWord.forEach((character) => {
207
+ if (character.letter === letter) {
208
+ character.show = true
209
+ }
210
+ })
211
+ } else {
212
+ // checks if the user is already out of strikes
213
+ if (this.onStrike <= 6) {
214
+ this.sevenStrikesCounter[this.onStrike].strike = true
215
+ this.onStrike = this.onStrike + 1
216
+ }
217
+ }
218
+ const isComplete = this.checkIfComplete()
219
+ const checkStrikes = this.checkStrikes()
220
+ if (isComplete) {
221
+ this.thatIsCorrect()
222
+ }
223
+ if (checkStrikes) {
224
+ this.thatIsIncorrect()
225
+ }
226
+ }
227
+ },
228
+ checkIfComplete() {
229
+ // checks to see if the user guessed all the letters correctly
230
+ let finished = true
231
+ this.block.metadata.config.words[this.onSlide].splitWord.forEach(
232
+ (element) => {
233
+ if (element.show === false) {
234
+ finished = false
235
+ }
236
+ }
237
+ )
238
+ return finished
239
+ },
240
+ checkStrikes() {
241
+ // checks to see if user has exhausted strikes
242
+ let outOfStrikes = true
243
+ this.sevenStrikesCounter.forEach((element) => {
244
+ if (element.strike === false) {
245
+ outOfStrikes = false
246
+ }
247
+ })
248
+ return outOfStrikes
249
+ },
250
+ thatIsIncorrect() {
251
+ this.showFeedback = true
252
+ // updates class
253
+ this.feedbackStatus = 'error'
254
+ // gets custom or standard feedback
255
+ if (
256
+ !_.isEmpty(this.block.metadata.config.feedback_incorrect) &&
257
+ this.block.metadata.config.feedback_incorrect !== ''
258
+ ) {
259
+ this.feedback = this.block.metadata.config.feedback_incorrect
260
+ } else {
261
+ this.feedback = this.$t(
262
+ 'plugin.games.components.content.blocks.word_jumble.incorrect'
263
+ )
264
+ }
265
+ },
266
+ thatIsCorrect() {
267
+ this.showFeedback = true
268
+ this.feedbackStatus = 'success'
269
+ if (
270
+ !_.isEmpty(this.block.metadata.config.feedback_correct) &&
271
+ this.block.metadata.config.feedback_correct !== ''
272
+ ) {
273
+ this.feedback = this.block.metadata.config.feedback_correct
274
+ } else {
275
+ this.feedback = this.$t(
276
+ 'plugin.games.components.content.blocks.word_jumble.correct'
277
+ )
278
+ }
279
+ },
280
+ onHideFeedback() {
281
+ // fired from feedback x
282
+ this.showFeedback = false
283
+ this.onSlideChanged(this.onSlide)
284
+ },
285
+ onInputChange(input) {
286
+ // catches target input
287
+ this.input = input.target.value
288
+ },
289
+ onSlideChanged(event) {
290
+ // this function is called when the slide is changed
291
+ // manually resetting is necessary due to fact that components do not remount
292
+ // event hold slide index
293
+ this.onSlide = event
294
+ // hides feedback if showing
295
+ this.showFeedback = false
296
+ this.feedbackStatus = ''
297
+ this.onStrike = 0
298
+ this.feedback = this.$t(
299
+ 'plugin.games.components.content.blocks.word_jumble.feedback'
300
+ )
301
+ // resets strikes
302
+ this.sevenStrikesCounter.forEach((element) => {
303
+ element.strike = false
304
+ })
305
+ if (
306
+ this.block.metadata.config.words[this.onSlide].splitWord &&
307
+ this.block.metadata.config.words[this.onSlide].splitWord
308
+ .length > 0
309
+ ) {
310
+ // rehides the displayed correct letters
311
+ this.block.metadata.config.words[
312
+ this.onSlide
313
+ ].splitWord.forEach((word) => {
314
+ if (word.show) {
315
+ word.show = false
316
+ }
317
+ })
318
+ }
319
+ },
320
+ onRevealAnswer() {
321
+ this.block.metadata.config.words[this.onSlide].splitWord.forEach(
322
+ (word) => {
323
+ word.show = true
324
+ }
325
+ )
326
+ },
327
+ },
328
+ }
329
+ </script>
330
+
331
+ <style scoped>
332
+ .outline {
333
+ border: 2px solid black;
334
+ border-radius: 10px;
335
+ }
336
+ .clueAndJumble {
337
+ font-size: 20px;
338
+ }
339
+ .spanBold {
340
+ font-weight: 900;
341
+ }
342
+ .error {
343
+ border: dashed 2px #dc3d1a;
344
+ background-color: #fff1f1;
345
+ }
346
+ .success {
347
+ border: dashed 2px #76b778;
348
+ background-color: #f1fff3;
349
+ }
350
+ .textArea {
351
+ width: 20px;
352
+ height: 20px;
353
+ border-bottom: 2px solid black;
354
+ display: flex;
355
+ justify-content: center;
356
+ align-items: center;
357
+ }
358
+ .strikeArea {
359
+ width: 40px;
360
+ height: 40px;
361
+ border: 2px solid black;
362
+ }
363
+ .strike {
364
+ color: #dc3d1a;
365
+ font-size: 40px;
366
+ margin: 10px;
367
+ }
368
+ </style>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div :class="keyboardClass"></div>
3
+ </template>
4
+
5
+ <script>
6
+ import Keyboard from 'simple-keyboard'
7
+ import 'simple-keyboard/build/css/index.css'
8
+
9
+ export default {
10
+ name: 'SimpleKeyboard',
11
+ props: {
12
+ keyboardClass: {
13
+ default: 'simple-keyboard',
14
+ type: String,
15
+ },
16
+ input: {
17
+ type: String,
18
+ },
19
+ },
20
+ data: () => ({
21
+ keyboard: null,
22
+ }),
23
+ mounted() {
24
+ this.keyboard = new Keyboard(this.keyboardClass, {
25
+ layout: {
26
+ default: [
27
+ 'q w e r t y u i o p',
28
+ 'a s d f g h j k l',
29
+ 'z x c v b n m',
30
+ ],
31
+ },
32
+ onChange: this.onChange,
33
+ onKeyPress: this.onKeyPress,
34
+ })
35
+ },
36
+ methods: {
37
+ onChange(input) {
38
+ this.$emit('onChange', input)
39
+ },
40
+ onKeyPress(button) {
41
+ this.$emit('onKeyPress', button)
42
+
43
+ /**
44
+ * If you want to handle the shift and caps lock buttons
45
+ */
46
+ if (button === '{shift}' || button === '{lock}') this.handleShift()
47
+ },
48
+ handleShift() {
49
+ let currentLayout = this.keyboard.options.layoutName
50
+ let shiftToggle = currentLayout === 'default' ? 'shift' : 'default'
51
+
52
+ this.keyboard.setOptions({
53
+ layoutName: shiftToggle,
54
+ })
55
+ },
56
+ },
57
+ watch: {
58
+ input(input) {
59
+ this.keyboard.setInput(input)
60
+ },
61
+ },
62
+ }
63
+ </script>
64
+
65
+ <!-- Add "scoped" attribute to limit CSS to this component only -->
66
+ <style scoped>
67
+ .hg-theme-default {
68
+ padding: 55px;
69
+ margin-top: 25px;
70
+ }
71
+ </style>
@@ -9,7 +9,6 @@
9
9
  class="textArea ml-1 mr-1"
10
10
  maxlength="1"
11
11
  @input="onInput($event, index)"
12
- v-on:keydown="onSpaceHandler($event)"
13
12
  ></v-text-field>
14
13
  </div>
15
14
  </v-row>
@@ -43,8 +42,8 @@ export default {
43
42
  handler(newValue) {
44
43
  // removes students answer from input if they got it correct
45
44
  this.splitUpWord()
46
- }
47
- }
45
+ },
46
+ },
48
47
  },
49
48
  data() {
50
49
  return {
@@ -71,13 +70,6 @@ export default {
71
70
  }
72
71
  }
73
72
  },
74
- onSpaceHandler(event) {
75
- // prevents empty space from being entered
76
- if (event.keyCode === 32) {
77
- event.preventDefault()
78
- return false
79
- }
80
- },
81
73
  onInput(event, index) {
82
74
  // handles focusing on next element after student enters a letter
83
75
  if (!_.isEmpty(event) && this.$refs['input' + (index + 1)]) {
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div>
3
- <v-form ref="form" v-model="valid" v-if="!loading">
3
+ <v-form ref="form" v-model="valid">
4
4
  <v-text-field
5
5
  v-model="block.metadata.config.title"
6
6
  :counter="50"
@@ -82,6 +82,8 @@
82
82
  <v-textarea
83
83
  v-if="item.expand"
84
84
  outlined
85
+ :counter="20"
86
+ maxlength="20"
85
87
  :autofocus="true"
86
88
  :label="
87
89
  $t(
@@ -96,15 +98,16 @@
96
98
  <v-textarea
97
99
  v-if="item.expand"
98
100
  outlined
101
+ :counter="155"
102
+ maxlength="155"
99
103
  :label="
100
104
  $t(
101
- 'plugin.games.components.settings.crossword.description'
105
+ 'plugin.games.components.settings.crossword.clue'
102
106
  )
103
107
  "
104
108
  class="pt-4"
105
109
  v-model="
106
- block.metadata.config.words[index]
107
- .description
110
+ block.metadata.config.words[index].clue
108
111
  "
109
112
  ></v-textarea>
110
113
  </v-card>
@@ -131,14 +134,6 @@
131
134
  </v-expansion-panel>
132
135
  </v-expansion-panels>
133
136
  </v-form>
134
- <div v-if="loading" class="text-center">
135
- <v-progress-circular
136
- :size="70"
137
- :width="7"
138
- color="primary"
139
- indeterminate
140
- ></v-progress-circular>
141
- </div>
142
137
  </div>
143
138
  </template>
144
139
 
@@ -168,11 +163,12 @@ export default {
168
163
  if (_.isEmpty(this.block.metadata.config.words)) {
169
164
  this.block.metadata.config.words = []
170
165
  let counter = 0
166
+ // ensures that there will always be at least two crossword elements to edit
171
167
  while (counter < 2) {
172
168
  const default_object = {
173
169
  id: counter,
174
170
  word: '',
175
- description: '',
171
+ clue: '',
176
172
  expand: false,
177
173
  }
178
174
  counter = counter + 1
@@ -183,19 +179,20 @@ export default {
183
179
  data() {
184
180
  return {
185
181
  valid: true,
186
- loading: false,
187
182
  cursor: null,
188
183
  }
189
184
  },
190
185
  methods: {
191
186
  onAddElement() {
187
+ // closes all elements that were expanded
192
188
  this.block.metadata.config.words.forEach((element) => {
193
189
  element.expand = false
194
190
  })
191
+ // pushes in new crossword object and expands element
195
192
  const default_object = {
196
193
  id: this.block.metadata.config.words.length,
197
194
  word: '',
198
- description: '',
195
+ clue: '',
199
196
  expand: true,
200
197
  }
201
198
  this.block.metadata.config.words.push(default_object)
@@ -28,13 +28,13 @@
28
28
  <v-expansion-panels flat>
29
29
  <v-expansion-panel>
30
30
  <v-expansion-panel-header class="pa-0">
31
- <h3>
31
+ <h5>
32
32
  {{
33
33
  $t(
34
34
  'plugin.games.components.settings.multiple_choice.questions'
35
35
  )
36
36
  }}
37
- </h3></v-expansion-panel-header
37
+ </h5></v-expansion-panel-header
38
38
  >
39
39
  <v-expansion-panel-content class="ma-0">
40
40
  <v-container class="pa-0">
@@ -96,7 +96,7 @@
96
96
  @mouseover="onHover"
97
97
  @mouseleave="onHoverLeave"
98
98
  @click="onOpenModal"
99
- v-on:keyup.enter="onOpenModal"
99
+ v-on:keydown.enter="onOpenModal"
100
100
  class="fullWidth"
101
101
  :class="cursor"
102
102
  tabindex="0"
@@ -149,6 +149,7 @@ import draggable from 'vuedraggable'
149
149
  import _ from 'lodash'
150
150
  import Dialog from '~/components/Dialog.vue'
151
151
  import QuestionDialog from '../content/blocks/multipleChoice/QuestionDialog.vue'
152
+ import Crypto from '~/helpers/Crypto'
152
153
 
153
154
  export default {
154
155
  name: 'MultipleChoiceSettingsManager',
@@ -195,24 +196,32 @@ export default {
195
196
  // when left the saveAndNew button it was causing errors
196
197
  this.isNotEditing = false
197
198
  } else {
199
+ let firstAnswerId = Crypto.id()
198
200
  this.block.metadata.config.questions.push({
201
+ // crypto.id for all ids and then use filters
199
202
  answer_options: [
200
203
  {
201
- id: 1,
204
+ id: firstAnswerId,
202
205
  value: '',
203
206
  correctAnswer: true,
204
207
  disabled: false,
208
+ chosen: false,
209
+ focus: false,
205
210
  },
206
211
  {
207
- id: 2,
212
+ id: Crypto.id(),
208
213
  value: '',
209
214
  correctAnswer: false,
210
215
  disabled: false,
216
+ chosen: false,
217
+ focus: false,
211
218
  },
212
219
  ],
220
+ correctAnswer: firstAnswerId,
213
221
  body: '',
214
222
  hint: '',
215
223
  answer_description: '',
224
+ id: Crypto.id(),
216
225
  })
217
226
  this.editingIndex =
218
227
  this.block.metadata.config.questions.length - 1
@@ -222,21 +231,26 @@ export default {
222
231
  this.dialog = true
223
232
  },
224
233
  saveAndNewCalled() {
234
+ let firstAnswerId = Crypto.id()
225
235
  this.block.metadata.config.questions.push({
226
236
  answer_options: [
227
237
  {
228
- id: 1,
238
+ id: firstAnswerId,
229
239
  value: '',
230
240
  correctAnswer: true,
231
241
  disabled: false,
242
+ chosen: false,
232
243
  },
233
244
  {
234
- id: 2,
245
+ id: Crypto.id(),
235
246
  value: '',
236
247
  correctAnswer: false,
237
248
  disabled: false,
249
+ chosen: false,
238
250
  },
239
251
  ],
252
+ // needed to add this bc radio group needs the id of the answer in answerobjects array to set correct value
253
+ correctAnswer: firstAnswerId,
240
254
  body: '',
241
255
  hint: '',
242
256
  answer_description: '',