@windward/games 0.9.0 → 0.11.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.
Files changed (25) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/content/blocks/crosswordPuzzle/CrosswordPuzzle.vue +2 -6
  3. package/components/content/blocks/dragDrop/BucketGame.vue +157 -87
  4. package/components/content/blocks/dragDrop/SortingGame.vue +42 -66
  5. package/components/content/blocks/flashcards/CardFace.vue +22 -57
  6. package/components/content/blocks/flashcards/Flashcard.vue +12 -13
  7. package/components/content/blocks/matchingGame/MatchingGame.vue +73 -117
  8. package/components/content/blocks/slideshow/SlideShow.vue +10 -24
  9. package/components/settings/BucketGameSettingsManager.vue +9 -32
  10. package/components/settings/FlashCardSlidesManager.vue +26 -91
  11. package/components/settings/MatchingGameManager.vue +14 -94
  12. package/components/settings/SlideShowManager.vue +11 -46
  13. package/components/settings/SortingGameSettingsManager.vue +2 -19
  14. package/i18n/en-US/components/content/blocks/bucket_game.ts +1 -0
  15. package/i18n/en-US/components/content/blocks/sorting_game.ts +2 -0
  16. package/i18n/en-US/components/settings/sorting_game.ts +2 -0
  17. package/i18n/es-ES/components/content/blocks/bucket_game.ts +1 -0
  18. package/i18n/es-ES/components/content/blocks/sorting_game.ts +2 -0
  19. package/i18n/es-ES/components/settings/sorting_game.ts +2 -0
  20. package/i18n/sv-SE/components/content/blocks/bucket_game.ts +1 -0
  21. package/i18n/sv-SE/components/content/blocks/sorting_game.ts +2 -0
  22. package/i18n/sv-SE/components/settings/sorting_game.ts +2 -0
  23. package/package.json +1 -1
  24. package/test/__mocks__/contentBlockMock.js +94 -0
  25. package/test/mocks.js +1 -0
@@ -1,25 +1,22 @@
1
1
  <template>
2
2
  <v-container>
3
3
  <br />
4
- <div class="card-content">
4
+ <div
5
+ class="card-content d-flex flex-column align-center justify-center"
6
+ >
5
7
  <v-row>
6
8
  <content-viewer :class="textClass" v-model="settings.text" />
7
9
  </v-row>
8
10
  <br />
9
- <v-row
10
- v-if="imagePublicUrl"
11
- no-gutters
12
- align="center"
13
- justify="center"
14
- >
15
- <v-img
16
- contain
17
- :aspect-ratio="16 / 9"
18
- :src="imagePublicUrl"
19
- :alt="imageAlt"
20
- max-height="210"
21
- max-width="600"
22
- />
11
+ <v-row v-if="settings.fileConfig" no-gutters>
12
+ <v-col>
13
+ <ImageAssetViewer
14
+ v-model="settings.fileConfig"
15
+ :assets="assets"
16
+ max-height="210"
17
+ max-width="250"
18
+ ></ImageAssetViewer>
19
+ </v-col>
23
20
  </v-row>
24
21
  <br />
25
22
  </div>
@@ -34,10 +31,11 @@
34
31
  import _ from 'lodash'
35
32
  import { MathHelper, ContentViewer } from '@windward/core/utils'
36
33
  import Uuid from '~/helpers/Uuid'
34
+ import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
37
35
 
38
36
  export default {
39
37
  name: 'CardFace',
40
- components: { ContentViewer },
38
+ components: { ContentViewer, ImageAssetViewer },
41
39
  props: {
42
40
  settings: { type: Object, required: true },
43
41
  side: { type: String, required: true },
@@ -46,7 +44,7 @@ export default {
46
44
  computed: {
47
45
  textClass() {
48
46
  if (
49
- !this.settings.imageAsset &&
47
+ !this.settings.fileConfig &&
50
48
  this.settings.text &&
51
49
  this.settings.text.length < 100 &&
52
50
  this.side === 'front'
@@ -58,7 +56,7 @@ export default {
58
56
  return 'text-center card-content--bold'
59
57
  }
60
58
  if (
61
- !this.settings.imageAsset &&
59
+ !this.settings.fileConfig &&
62
60
  this.settings.text &&
63
61
  (MathHelper.containsMathML(this.settings.text.length) ||
64
62
  MathHelper.containsLatex(this.settings.text.length))
@@ -68,51 +66,18 @@ export default {
68
66
 
69
67
  return 'px-6 text-left'
70
68
  },
71
- imageAsset() {
72
- if (!this.settings.imageAsset) {
73
- return null
74
- }
75
-
76
- let image = _.cloneDeep(this.settings.imageAsset)
77
-
78
- // If the file is linked then resolve it first
79
- // Otherwise it's a hard link that we can just return
80
- if (image && image.file_asset_id !== null) {
81
- image = this.getAssetByFileAssetId(image.file_asset_id)
82
- }
83
- return image
84
- },
85
- imagePublicUrl() {
86
- return _.get(this.imageAsset, 'asset.public_url', '')
87
- },
88
- imageAlt() {
89
- // Default to user defined alt but fallback to global alt
90
- if (this.settings.imageAlt) {
91
- return this.settings.imageAlt
92
- } else {
93
- return _.get(this.imageAsset, 'asset.metadata.props.alt', '')
94
- }
95
- },
96
69
  },
97
- methods: {
98
- getAssetByFileAssetId(fileAssetId) {
99
- // Not a uuid. Instant fail
100
- if (!Uuid.test(fileAssetId)) {
101
- return null
102
- }
103
- const foundAsset = this.assets.find((asset) => {
104
- return _.get(asset, 'file_asset_id', null) === fileAssetId
105
- })
106
-
107
- return foundAsset || null
108
- },
70
+ beforeMount() {
71
+ // so background isn't set to white by the imageassetviewer
72
+ if (_.isEmpty(this.settings.fileConfig)) {
73
+ this.settings.fileConfig = {}
74
+ this.settings.fileConfig.hideBackground = true
75
+ }
109
76
  },
110
77
  }
111
78
  </script>
112
79
 
113
80
  <style lang="scss" scoped>
114
- .card-header {
115
- }
116
81
  .card-content {
117
82
  color: var(--v-primary-base);
118
83
  font-size: 1rem;
@@ -1,6 +1,11 @@
1
1
  <template>
2
2
  <v-container style="height: 100%" class="div-container">
3
- <v-card outlined @click="toggleCard" v-show="side" :class="cardClass">
3
+ <v-card
4
+ outlined
5
+ @click="toggleCard"
6
+ v-show="side"
7
+ :class="cardClass"
8
+ >
4
9
  <v-card-text>
5
10
  <CardFace
6
11
  side="front"
@@ -51,15 +56,15 @@ export default {
51
56
  }
52
57
 
53
58
  if (
54
- this.frontFace.imageAsset === '' &&
55
- this.backFace.imageAsset === ''
59
+ this.frontFace.fileConfig === '' &&
60
+ this.backFace.fileConfig === ''
56
61
  ) {
57
62
  result = result + ' flashcard--size-md'
58
63
  }
59
64
 
60
65
  if (
61
- this.frontFace.imageAsset !== '' ||
62
- this.backFace.imageAsset !== ''
66
+ this.frontFace.fileConfig !== '' ||
67
+ this.backFace.fileConfig !== ''
63
68
  ) {
64
69
  result = result + ' flashcard--size-lg'
65
70
  }
@@ -68,10 +73,7 @@ export default {
68
73
  },
69
74
  frontFace() {
70
75
  return {
71
- imageAsset: this.options.front.imageAsset,
72
- imageAlt: this.options.front.imageAlt
73
- ? this.options.front.imageAlt
74
- : '',
76
+ fileConfig: this.options.front.fileConfig,
75
77
  text: this.options.front.text,
76
78
  header: this.options.front.header,
77
79
  footer: this.$t(
@@ -81,10 +83,7 @@ export default {
81
83
  },
82
84
  backFace() {
83
85
  return {
84
- imageAsset: this.options.back.imageAsset,
85
- imageAlt: this.options.back.imageAlt
86
- ? this.options.back.imageAlt
87
- : '',
86
+ fileConfig: this.options.back.fileConfig,
88
87
  text: this.options.back.text,
89
88
  header: this.options.back.header,
90
89
  footer: this.$t(
@@ -21,29 +21,28 @@
21
21
  {{ block.metadata.config.instructions }}
22
22
  </p>
23
23
  <v-container :class="status">
24
- <v-alert class="d-flex justify-center pa-2">{{
24
+ <v-row class="d-flex justify-center py-3">{{
25
25
  feedback
26
26
  ? feedback
27
27
  : $t(
28
28
  'windward.games.components.content.blocks.bucket_game.form.feedback.feedback_here'
29
29
  )
30
- }}</v-alert>
31
- <v-row class="d-flex justify-center pa-2">
30
+ }}</v-row>
31
+ <v-row class="d-flex justify-center pb-3">
32
32
  <v-btn
33
- v-if="status === 'successOutline'"
34
- class="success mr-5"
33
+ v-if="status === 'container-success-outline'"
34
+ class="success"
35
35
  elevation="0"
36
36
  @click="onContinueGame"
37
37
  >{{ $t('shared.forms.continue') }}
38
38
  </v-btn>
39
- <v-container
40
- v-if="status === 'errorOutline'"
41
- class="d-flex justify-center align-center"
39
+ <v-btn
40
+ v-if="status === 'container-error-outline'"
41
+ class="error"
42
+ elevation="0"
42
43
  @click="onExitFeedback"
43
- ><v-btn class="error" elevation="0">{{
44
- $t('shared.forms.try_again')
45
- }}</v-btn>
46
- </v-container>
44
+ >{{ $t('shared.forms.try_again') }}
45
+ </v-btn>
47
46
  </v-row>
48
47
  </v-container>
49
48
  <v-container class="pl-0 pr-0 mt-2">
@@ -79,17 +78,22 @@
79
78
  </v-card-text>
80
79
  </v-card>
81
80
  </v-row>
82
- <v-row align="center" class="col-md-10">
81
+ <v-row
82
+ align="center"
83
+ class="col-md-10 d-flex justify-center"
84
+ >
83
85
  <v-card
84
86
  v-if="mainPrompt['textOrImage'] === 'image'"
85
- class="pa-2 flex-fill bucket"
87
+ class="pa-2 flex-fill bucket d-flex justify-center"
86
88
  min-height="5em"
89
+ max-height="400"
90
+ max-width="300"
87
91
  outlined
88
92
  tile
89
93
  >
90
94
  <v-card-text>
91
95
  <draggable
92
- v-if="mainPrompt.file"
96
+ v-if="mainPrompt.fileConfig.asset"
93
97
  class="dragArea list-group"
94
98
  :key="mainPrompt['prompt']"
95
99
  v-bind="dragOptions"
@@ -97,28 +101,18 @@
97
101
  group="people"
98
102
  tabindex="0"
99
103
  >
100
- <div :id="mainPrompt.file.file_asset_id">
101
- <v-img
102
- :aria-describedby="
103
- getFileAriaDescribedBy(
104
- mainPrompt.file,
105
- mainPrompt[
106
- 'ariaDescribedBy'
107
- ]
108
- )
109
- "
110
- :alt="
111
- getFileAlt(
112
- mainPrompt.file,
113
- mainPrompt['altText']
114
- )
115
- "
116
- :src="
117
- getFilePublicUrl(
118
- mainPrompt.file
119
- )
120
- "
121
- ></v-img>
104
+ <div
105
+ :id="
106
+ mainPrompt.fileConfig.asset
107
+ .file_asset_id
108
+ "
109
+ >
110
+ <ImageAssetViewer
111
+ v-model="mainPrompt['fileConfig']"
112
+ :assets="block.assets"
113
+ max-height="400"
114
+ max-width="300"
115
+ ></ImageAssetViewer>
122
116
  </div>
123
117
  </draggable>
124
118
  </v-card-text>
@@ -172,7 +166,7 @@
172
166
  ].textOrImage === 'image' &&
173
167
  block.metadata.config.prompts[startingIndex][
174
168
  startingIndex
175
- ].file
169
+ ].fileConfig.asset
176
170
  "
177
171
  class="pa-2 flex-fill bucket"
178
172
  min-height="5em"
@@ -196,43 +190,18 @@
196
190
  :id="
197
191
  block.metadata.config.prompts[
198
192
  startingIndex
199
- ][startingIndex].file.file_asset_id
193
+ ][startingIndex].fileConfig.asset
194
+ .file_asset_id
200
195
  "
201
196
  >
202
- <v-img
203
- :aria-describedby="
204
- getFileAriaDescribedBy(
205
- block.metadata.config
206
- .prompts[startingIndex][
207
- startingIndex
208
- ].file,
209
- block.metadata.config
210
- .prompts[startingIndex][
211
- startingIndex
212
- ]['ariaDescribedBy']
213
- )
197
+ <ImageAssetViewer
198
+ v-model="
199
+ block.metadata.config.prompts[
200
+ startingIndex
201
+ ][startingIndex].fileConfig
214
202
  "
215
- :alt="
216
- getFileAlt(
217
- block.metadata.config
218
- .prompts[startingIndex][
219
- startingIndex
220
- ].file,
221
- block.metadata.config
222
- .prompts[startingIndex][
223
- startingIndex
224
- ]['altText']
225
- )
226
- "
227
- :src="
228
- getFilePublicUrl(
229
- block.metadata.config
230
- .prompts[startingIndex][
231
- startingIndex
232
- ].file
233
- )
234
- "
235
- ></v-img>
203
+ :assets.sync="block.assets"
204
+ ></ImageAssetViewer>
236
205
  </div>
237
206
  </draggable>
238
207
  </v-card-text>
@@ -257,8 +226,8 @@
257
226
  .answerObjects"
258
227
  :key="aindex"
259
228
  :class="
260
- 'pa-2 ma-2 answerCard container-outline ' +
261
- answerColor(answer)
229
+ 'pa-2 ma-2 card-answer-option container-outline ' +
230
+ getAnswerStyles(answer)
262
231
  "
263
232
  outlined
264
233
  >
@@ -274,7 +243,6 @@
274
243
  align="end"
275
244
  >
276
245
  <v-row>
277
- <!-- <v-flex xs8></v-flex> -->
278
246
  <v-col cols="12" lg="6" md="7" sm="6"></v-col>
279
247
  <v-col cols="12" lg="6" md="5" sm="6">
280
248
  <v-row
@@ -311,11 +279,8 @@
311
279
  outlined
312
280
  elevation="0"
313
281
  @click="onReset"
314
- >{{
315
- $t(
316
- 'windward.games.components.content.blocks.matching_game.reset'
317
- )
318
- }}
282
+ >
283
+ {{ $t('shared.forms.reset') }}
319
284
  </v-btn>
320
285
  </v-col>
321
286
  </v-row>
@@ -331,10 +296,11 @@
331
296
  import _ from 'lodash'
332
297
  import draggable from 'vuedraggable'
333
298
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
299
+ import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
334
300
 
335
301
  export default {
336
302
  name: 'MatchingGame',
337
- components: { draggable },
303
+ components: { draggable, ImageAssetViewer },
338
304
  extends: BaseContentBlock,
339
305
  beforeMount() {
340
306
  if (_.isEmpty(this.block)) {
@@ -405,9 +371,10 @@ export default {
405
371
  status: 'default',
406
372
  allowDrag: true,
407
373
  mainPrompt: {
408
- altText: '',
409
374
  answer: {},
410
- file: null,
375
+ fileConfig: {
376
+ hideBackground: true,
377
+ },
411
378
  prompt: '',
412
379
  textOrImage: 'text',
413
380
  },
@@ -432,14 +399,20 @@ export default {
432
399
  this.setMainPrompt()
433
400
  },
434
401
  methods: {
435
- answerColor(element) {
402
+ getAnswerStyles(element) {
403
+ let classValue = ' '
436
404
  if (
437
405
  !_.isEmpty(this.droppedElement) &&
438
406
  element.display === this.droppedElement.display
439
407
  ) {
440
- return this.status
408
+ classValue += this.status + ' '
409
+ }
410
+ if (this.allowDrag) {
411
+ classValue += 'card-answer-option-grab '
412
+ } else {
413
+ classValue += 'card-answer-option-default '
441
414
  }
442
- return ''
415
+ return classValue
443
416
  },
444
417
  shuffle(array) {
445
418
  let currentIndex = array.length,
@@ -498,7 +471,7 @@ export default {
498
471
  (outerElement) => {
499
472
  const mainElement = outerElement.find((element) => {
500
473
  return (
501
- element.file?.file_asset_id ==
474
+ element.fileConfig.asset?.file_asset_id ==
502
475
  evt.to.firstElementChild.id
503
476
  )
504
477
  })
@@ -514,7 +487,7 @@ export default {
514
487
  this.droppedElement = null
515
488
  } else if (draggedElement.id !== target.answer.id) {
516
489
  this.feedback = this.block.metadata.config.feedback_incorrect
517
- this.status = 'errorOutline'
490
+ this.status = 'container-error-outline'
518
491
  this.allowDrag = false
519
492
  this.droppedElement = draggedElement
520
493
  } else if (draggedElement.id === target.answer.id) {
@@ -522,7 +495,7 @@ export default {
522
495
  ? target.matchExplanation
523
496
  : this.block.metadata.config.feedback_correct
524
497
  this.solvedQuestions.push(target)
525
- this.status = 'successOutline'
498
+ this.status = 'container-success-outline'
526
499
  this.allowDrag = false
527
500
  this.droppedElement = draggedElement
528
501
  if (
@@ -568,28 +541,6 @@ export default {
568
541
  )
569
542
  this.setMainPrompt()
570
543
  },
571
- getFileAlt(file, defaultText = '') {
572
- // If a default / override was defined
573
- if (defaultText) {
574
- return defaultText
575
- }
576
-
577
- file = this.resolveAsset(file)
578
- return _.get(file, 'asset.metadata.props.alt', '')
579
- },
580
- getFileAriaDescribedBy(file, defaultText = '') {
581
- // If a default / override was defined
582
- if (defaultText) {
583
- return defaultText
584
- }
585
-
586
- file = this.resolveAsset(file)
587
- return _.get(file, 'asset.metadata.props.aria_describedby', '')
588
- },
589
- getFilePublicUrl(file) {
590
- file = this.resolveAsset(file)
591
- return _.get(file, 'asset.public_url', '')
592
- },
593
544
  },
594
545
  }
595
546
  </script>
@@ -600,34 +551,39 @@ export default {
600
551
  box-shadow: 0px 2px 3px #0000004a;
601
552
  transition: 0.5s box-shadow;
602
553
  }
603
- .successOutline {
554
+ .container-success-outline {
604
555
  border: 4px solid var(--v-success-base) !important;
605
556
  }
606
- .errorOutline {
557
+ .container-error-outline {
607
558
  border: 4px solid var(--v-error-base) !important;
608
559
  }
609
560
  .card_text {
610
561
  font-size: large;
611
562
  }
612
- .answerCard {
563
+ .card-answer-option {
613
564
  min-width: 23%;
565
+ }
566
+ .card-answer-option-grab {
614
567
  cursor: grab;
615
568
  }
569
+ .card-answer-option-default {
570
+ cursor: default;
571
+ }
616
572
  .icon--error {
617
573
  color: var(--v-error-base);
618
574
  }
619
575
  @media (max-width: 1270px) {
620
- .answerCard {
576
+ .card-answer-option {
621
577
  min-width: 30%;
622
578
  }
623
579
  }
624
580
  @media (max-width: 900px) {
625
- .answerCard {
581
+ .card-answer-option {
626
582
  min-width: 40%;
627
583
  }
628
584
  }
629
585
  @media (max-width: 610px) {
630
- .answerCard {
586
+ .card-answer-option {
631
587
  min-width: 95%;
632
588
  }
633
589
  }
@@ -69,22 +69,15 @@
69
69
  {{ slide['description'] }}
70
70
  </p>
71
71
  </div>
72
- <div
73
- style="overflow: auto"
74
- class="pl-15 pr-15 container-image"
75
- >
76
- <v-img
77
- v-if="slide['imageAsset']"
78
- :src="getImageUrl(slide['imageAsset'])"
79
- :alt="
80
- getImageAlt(
81
- slide['imageAsset'],
82
- slide['imageAlt']
83
- )
72
+ <div class="pl-15 pr-15 container-image">
73
+ <ImageAssetViewer
74
+ v-model="
75
+ block.metadata.config.slides[index]
76
+ .fileConfig
84
77
  "
85
- height="100%"
86
- contain
87
- />
78
+ :assets="block.assets"
79
+ max-height="300"
80
+ ></ImageAssetViewer>
88
81
  </div>
89
82
  </v-sheet>
90
83
  <div class="d-flex justify-end">
@@ -108,11 +101,12 @@
108
101
  <script>
109
102
  import _ from 'lodash'
110
103
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
111
- import Crypto from '~/helpers/Crypto'
104
+ import ImageAssetViewer from '~/components/Content/ImageAssetViewer.vue'
112
105
 
113
106
  export default {
114
107
  name: 'SlideShow',
115
108
  extends: BaseContentBlock,
109
+ components: { ImageAssetViewer },
116
110
  beforeMount() {
117
111
  if (_.isEmpty(this.block)) {
118
112
  this.block = {}
@@ -154,14 +148,6 @@ export default {
154
148
  file = this.resolveAsset(file)
155
149
  return _.get(file, 'asset.public_url', '')
156
150
  },
157
- getImageAlt(file, defaultText = '') {
158
- // If a default / override was defined
159
- if (defaultText) {
160
- return defaultText
161
- }
162
- file = this.resolveAsset(file)
163
- return _.get(file, 'asset.metadata.props.alt', '')
164
- },
165
151
  },
166
152
  }
167
153
  </script>
@@ -71,6 +71,7 @@
71
71
  block.metadata.config.bucket_titles[index]
72
72
  .title
73
73
  "
74
+ :data-key="'bucket-' + index + '-title'"
74
75
  :disabled="render"
75
76
  ></TextEditor>
76
77
  </v-flex>
@@ -143,6 +144,13 @@
143
144
  answerIndex
144
145
  ].display
145
146
  "
147
+ :data-key="
148
+ 'bucket-answer-' +
149
+ index +
150
+ '-' +
151
+ answerIndex +
152
+ '-display'
153
+ "
146
154
  :disabled="render"
147
155
  ></TextEditor>
148
156
  <p class="p-label mb-0">
@@ -201,31 +209,6 @@
201
209
  }}
202
210
  </v-btn>
203
211
  </v-row>
204
- <v-textarea
205
- v-model="block.metadata.config.feedback_correct"
206
- class="pt-4"
207
- outlined
208
- auto-grow
209
- :counter="255"
210
- :label="
211
- $t(
212
- 'windward.games.components.settings.bucket_game.form.feedback.correct'
213
- )
214
- "
215
- :disabled="render"
216
- ></v-textarea>
217
- <v-textarea
218
- v-model="block.metadata.config.feedback_incorrect"
219
- outlined
220
- auto-grow
221
- :counter="255"
222
- :label="
223
- $t(
224
- 'windward.games.components.settings.bucket_game.form.feedback.incorrect'
225
- )
226
- "
227
- :disabled="render"
228
- ></v-textarea>
229
212
  </v-container>
230
213
  <div v-if="loading" class="text-center">
231
214
  <v-progress-circular
@@ -290,12 +273,6 @@ export default {
290
273
  'windward.games.components.settings.bucket_game.form.default_instructions'
291
274
  )
292
275
  }
293
- if (_.isEmpty(this.block.metadata.config.feedback_correct)) {
294
- this.block.metadata.config.feedback_correct = ''
295
- }
296
- if (_.isEmpty(this.block.metadata.config.feedback_incorrect)) {
297
- this.block.metadata.config.feedback_incorrect = ''
298
- }
299
276
  if (_.isEmpty(this.block.metadata.config.bucket_titles)) {
300
277
  this.block.metadata.config.bucket_titles = []
301
278
  this.onAddBucket()
@@ -354,7 +331,7 @@ export default {
354
331
  title: '',
355
332
  color: colors.blueGrey.lighten5,
356
333
  expand: true,
357
- nested_answers: []
334
+ nested_answers: [],
358
335
  }
359
336
  this.block.metadata.config.bucket_titles.push(emptyString)
360
337
  },