fcad-core-dragon 2.0.0-beta.4 → 2.0.0-beta.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 (44) hide show
  1. package/CHANGELOG +377 -373
  2. package/package.json +61 -61
  3. package/src/$locales/en.json +3 -1
  4. package/src/$locales/fr.json +3 -1
  5. package/src/components/AppBase.vue +20 -17
  6. package/src/components/AppBaseButton.test.js +22 -0
  7. package/src/components/AppBaseButton.vue +11 -5
  8. package/src/components/AppBaseModule.vue +48 -27
  9. package/src/components/AppBasePage.vue +7 -44
  10. package/src/components/AppCompAudio.vue +31 -0
  11. package/src/components/AppCompBranchButtons.vue +20 -16
  12. package/src/components/AppCompButtonProgress.vue +4 -9
  13. package/src/components/AppCompCarousel.vue +120 -90
  14. package/src/components/AppCompInputCheckBoxNext.vue +5 -0
  15. package/src/components/AppCompInputDropdownNext.vue +50 -8
  16. package/src/components/AppCompInputTextNext.vue +21 -2
  17. package/src/components/AppCompInputTextTableNext.vue +1 -0
  18. package/src/components/AppCompInputTextToFillDropdownNext.vue +8 -0
  19. package/src/components/AppCompMenu.vue +428 -423
  20. package/src/components/AppCompNavigation.vue +41 -28
  21. package/src/components/AppCompNoteCall.vue +64 -38
  22. package/src/components/AppCompNoteCredit.vue +303 -105
  23. package/src/components/AppCompPlayBar.vue +4 -5
  24. package/src/components/AppCompPlayBarNext.vue +20 -12
  25. package/src/components/AppCompPopUpNext.vue +1 -4
  26. package/src/components/AppCompQuizNext.vue +8 -4
  27. package/src/components/AppCompQuizRecall.vue +44 -22
  28. package/src/components/AppCompTableOfContent.vue +61 -62
  29. package/src/components/AppCompViewDisplay.vue +6 -6
  30. package/src/components/BaseModule.vue +1 -18
  31. package/src/components/tests__/AppBaseButton.spec.js +53 -0
  32. package/src/main.js +3 -3
  33. package/src/module/stores/appStore.js +58 -5
  34. package/src/module/xapi/Crypto/index.js +53 -53
  35. package/src/module/xapi/Statement/activity.js +47 -47
  36. package/src/module/xapi/Statement/group.js +26 -26
  37. package/src/module/xapi/Statement/statementRef.js +23 -23
  38. package/src/module/xapi/Statement/substatement.js +22 -22
  39. package/src/module/xapi/Statement/verb.js +36 -36
  40. package/src/module/xapi/activitytypes.js +17 -17
  41. package/src/plugins/helper.js +53 -12
  42. package/src/router/index.js +6 -1
  43. package/src/shared/validators.js +36 -179
  44. package/vitest.config.js +19 -0
@@ -165,7 +165,38 @@ export default {
165
165
  },
166
166
  errorHandling(e) {
167
167
  this.hasSourceLoadingError = true
168
+ },
169
+ // ===================================TEST TRANSCRIP================================================
170
+ /**
171
+ * @description Fetching method for transcript
172
+ * @summary Fetch a transcript from HTML file to display. File URL is defined in the media data
173
+ * and return its content for display
174
+ * @return {String} HTML string
175
+ */
176
+ async fetchTranscript() {
177
+ try {
178
+ //const tFile = 'exemple_transcript2.html'
179
+ const { mTranscript } = this.mediaToPlay
180
+ const tFile = mTranscript
181
+ if (!tFile) throw new Error('Missing transcript File!')
182
+
183
+ // validate file types. Only .html are allowed
184
+ if (!tFile.endsWith('.html'))
185
+ throw new Error(
186
+ 'Invalid valid transcript file. \n Expecting .html file'
187
+ )
188
+
189
+ const fileUrl = !tFile.includes('/') ? `./${tFile}` : tFile //allow passing file in Public folder
190
+ // const fileUrl = new URL(tFile, import.meta.url))
191
+ const res = await axios.get(fileUrl, { responseType: 'blob' })
192
+ const content = await res.data.text()
193
+
194
+ return content
195
+ } catch (err) {
196
+ console.warn("YOU'VE GOT AN ERROR!\n", err)
197
+ }
168
198
  }
199
+ // ===================================TEST TRANSCRIP================================================
169
200
  }
170
201
  }
171
202
  </script>
@@ -38,7 +38,7 @@ Si la composante est appelée sans prop, les boutons par défaut seront génér
38
38
  <v-row v-else>
39
39
  <v-col
40
40
  v-for="branch of branchsStateData"
41
- :id="branch.id"
41
+ :id="`branch_${branch.id}`"
42
42
  :key="branch.id"
43
43
  class="branch-btn"
44
44
  >
@@ -65,24 +65,20 @@ Si la composante est appelée sans prop, les boutons par défaut seront génér
65
65
  v-else-if="isCard && cards.length"
66
66
  class="branch-btn-wrapper branch-btn-card"
67
67
  >
68
- <v-card class="mx-auto" max-width="20rem">
69
- <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover>
70
- <v-toolbar color="transparent">
71
- <template #append>
72
- <app-comp-button-progress
73
- :set-target="sidebar"
74
- :percent="branch.progression ? branch.progression : 0"
75
- :branch-data="branch"
76
- :btn-title="getMatchingElement(branch.id).btnTitle"
77
- ></app-comp-button-progress>
78
- </template>
79
- </v-toolbar>
80
- </v-img>
68
+ <v-card class="mx-auto" max-width="15rem">
69
+ <v-img :src="branch.imgFile" :alt="`${branch.imgAlt}`" cover />
81
70
 
82
71
  <v-card-title>{{ branch.title }}</v-card-title>
83
72
  <v-card-text>
84
73
  {{ branch.text }}
85
74
  </v-card-text>
75
+
76
+ <app-comp-button-progress
77
+ :set-target="sidebar"
78
+ :percent="branch.progression ? branch.progression : 0"
79
+ :branch-data="branch"
80
+ :btn-title="getMatchingElement(branch.id).btnTitle"
81
+ ></app-comp-button-progress>
86
82
  </v-card>
87
83
  </div>
88
84
  <div v-else class="branch-btn-wrapper">
@@ -98,7 +94,6 @@ Si la composante est appelée sans prop, les boutons par défaut seront génér
98
94
  </v-row>
99
95
  </template>
100
96
  <script>
101
- // ...
102
97
  import { mapState } from 'pinia'
103
98
  import { useAppStore } from '../module/stores/appStore'
104
99
  import AppCompButtonProgress from './AppCompButtonProgress.vue'
@@ -304,7 +299,6 @@ export default {
304
299
  let { state = 'new', progression = 0 } = this.getBranchProgression(
305
300
  branchData.id
306
301
  )
307
- // if (!state || !progression) return
308
302
 
309
303
  branchData.state = state
310
304
  branchData.progression = progression
@@ -544,9 +538,19 @@ export default {
544
538
  <style lang="scss">
545
539
  .branch-btn-wrapper {
546
540
  position: relative;
541
+
547
542
  .branch-btn-custom {
548
543
  background-color: transparent;
549
544
  border: none;
550
545
  }
551
546
  }
547
+
548
+ .v-card {
549
+ position: relative;
550
+ .button-progress-wrapper {
551
+ position: absolute;
552
+ right: 55px;
553
+ top: 15px;
554
+ }
555
+ }
552
556
  </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="button-progress-wrapper">
3
- <button
3
+ <app-base-button
4
4
  :id="`btn_branch_${branchData.id}`"
5
5
  :target-ref="branchData.id"
6
6
  :title="btnTitle || $t('text.place_holder.for_title_btn_progress')"
@@ -17,11 +17,13 @@
17
17
  <svg v-if="branchData.state != status.COMPLETE">
18
18
  <use href="#navigate-next-icon"></use>
19
19
  </svg>
20
- </button>
20
+ </app-base-button>
21
21
  </div>
22
22
  </template>
23
23
  <script>
24
+ import AppBaseButton from './AppBaseButton.vue'
24
25
  export default {
26
+ components: { AppBaseButton },
25
27
  props: {
26
28
  mini: {
27
29
  type: Boolean,
@@ -63,7 +65,6 @@ export default {
63
65
  },
64
66
 
65
67
  created() {
66
- // if (isNaN(this.percent)) this.percent = 0
67
68
  this.$bus.$on('branching-hidden', this.resetIsActive)
68
69
  },
69
70
 
@@ -98,12 +99,6 @@ export default {
98
99
  .button-progress-wrapper {
99
100
  &:not(.card-btn) {
100
101
  .branch-btn-default {
101
- // left: 4px;
102
- // top: 4px;
103
- // min-width: 42px;
104
- // min-height: 42px;
105
- right: 15px !important;
106
- top: -15px !important;
107
102
  min-width: 42px;
108
103
  min-height: 42px;
109
104
  }
@@ -3,82 +3,95 @@
3
3
  @ What it does: Create an html element including an array of slides. These slides are objects including required property (imgSrc) and optional properties (imgAlt, Title, Hypertext).
4
4
  -->
5
5
 
6
- <template>
6
+ <template v-if="slides">
7
7
  <section id="carousel" :aria-label="$t('text.carousel')">
8
- <div class="carousel-inner">
9
- <div id="mycarousel-slides" class="carousel-slides" aria-live="polite">
10
- <div
11
- v-for="(slide, index) in slides"
12
- :key="index"
13
- class="carousel-slide"
14
- :class="{
15
- current: currentSlide == index + 1,
16
- prev: currentSlide >= index + 2,
17
- next: currentSlide <= index
18
- }"
19
- role="group"
20
- :aria-hidden="!(currentSlide == index + 1)"
21
- >
22
- <span class="sr-only">
23
- {{
24
- $t('text.slide') +
25
- ' ' +
26
- (index + 1) +
27
- ' ' +
28
- $t('text.of') +
29
- ' ' +
30
- slideLength
31
- }}
32
- </span>
8
+ <app-base-error-display
9
+ v-if="errorsSlider.length"
10
+ :error-group="'component'"
11
+ :error-title="'ERREUR: CRÉATION CAROUSEL'"
12
+ :errors-list="errorsSlider"
13
+ ></app-base-error-display>
14
+ <template v-else>
15
+ <div class="carousel-inner">
16
+ <div id="mycarousel-slides" class="carousel-slides" aria-live="polite">
33
17
  <div
34
- class="carousel-image"
35
- :class="{ 'full-width': !(slide.title || slide.hypertext) }"
18
+ v-for="(slide, index) in slides"
19
+ :key="index"
20
+ class="carousel-slide"
21
+ :class="{
22
+ current: currentSlide == index + 1,
23
+ prev: currentSlide >= index + 2,
24
+ next: currentSlide <= index
25
+ }"
26
+ role="group"
27
+ :aria-hidden="!(currentSlide == index + 1)"
36
28
  >
37
- <img
38
- :src="slide.imgSrc"
39
- :alt="slide.imgAlt"
40
- :aria-hidden="slide.imgAlt == ' ' || !slide.imgAlt ? true : false"
41
- />
42
- </div>
43
- <div v-if="slide.title || slide.hypertext" class="carousel-text">
44
- <h3 v-if="slide.title">
45
- {{ slide.title }}
46
- </h3>
47
- <div v-html="slide.hypertext"></div>
29
+ <span class="sr-only">
30
+ {{
31
+ $t('text.slide') +
32
+ ' ' +
33
+ (index + 1) +
34
+ ' ' +
35
+ $t('text.of') +
36
+ ' ' +
37
+ slideLength
38
+ }}
39
+ </span>
40
+ <div
41
+ class="carousel-image"
42
+ :class="{ 'full-width': !(slide.title || slide.hypertext) }"
43
+ >
44
+ <img
45
+ :src="slide.imgSrc"
46
+ :alt="slide.imgAlt"
47
+ :aria-hidden="
48
+ slide.imgAlt == ' ' || !slide.imgAlt ? true : false
49
+ "
50
+ />
51
+ </div>
52
+ <div v-if="slide.title || slide.hypertext" class="carousel-text">
53
+ <h3 v-if="slide.title">
54
+ {{ slide.title }}
55
+ </h3>
56
+ <div v-html="slide.hypertext"></div>
57
+ </div>
48
58
  </div>
49
59
  </div>
60
+
61
+ <div class="carousel-controls">
62
+ <app-base-button
63
+ id="carousel-btn-prev"
64
+ class="carousel-btn"
65
+ :aria-label="$t('button.carousel_prev')"
66
+ aria-controls="mycarousel-slides"
67
+ :title="$t('button.carousel_prev')"
68
+ :aria-disabled="disablePrev"
69
+ :is-disabled="disablePrev"
70
+ :disabled="disablePrev"
71
+ @click="prevSlide()"
72
+ >
73
+ <svg>
74
+ <use href="#fleche-gauche-icon"></use>
75
+ </svg>
76
+ </app-base-button>
77
+ <app-base-button
78
+ id="carousel-btn-next"
79
+ class="carousel-btn"
80
+ :aria-label="$t('button.carousel_next')"
81
+ aria-controls="mycarousel-slides"
82
+ :title="$t('button.carousel_next')"
83
+ :aria-disabled="disableNext"
84
+ :is-disabled="disableNext"
85
+ :disabled="disableNext"
86
+ @click="nextSlide()"
87
+ >
88
+ <svg>
89
+ <use href="#fleche-droite-icon"></use>
90
+ </svg>
91
+ </app-base-button>
92
+ </div>
50
93
  </div>
51
- <div class="carousel-controls">
52
- <app-base-button
53
- id="carousel-btn-prev"
54
- class="carousel-btn"
55
- :aria-label="$t('button.carousel_prev')"
56
- aria-controls="mycarousel-slides"
57
- :title="$t('button.carousel_prev')"
58
- :disabled="disablePrev"
59
- :aria-disabled="disablePrev"
60
- @click="prevSlide()"
61
- >
62
- <svg>
63
- <use href="#fleche-gauche-icon"></use>
64
- </svg>
65
- </app-base-button>
66
- <app-base-button
67
- id="carousel-btn-next"
68
- class="carousel-btn"
69
- :aria-label="$t('button.carousel_next')"
70
- aria-controls="mycarousel-slides"
71
- :title="$t('button.carousel_next')"
72
- :disabled="disableNext"
73
- :aria-disabled="disableNext"
74
- @click="nextSlide()"
75
- >
76
- <svg>
77
- <use href="#fleche-droite-icon"></use>
78
- </svg>
79
- </app-base-button>
80
- </div>
81
- </div>
94
+ </template>
82
95
  <div class="carousel-index">
83
96
  <p aria-hidden="true">{{ currentSlide }}/{{ slideLength }}</p>
84
97
  </div>
@@ -86,16 +99,20 @@
86
99
  </template>
87
100
 
88
101
  <script>
102
+ import AppBaseErrorDisplay from './AppBaseErrorDisplay.vue'
89
103
  export default {
90
104
  name: 'AppCompSlider',
105
+ components: { AppBaseErrorDisplay },
91
106
  props: {
92
- slides: { type: Array, required: true } //Array of slides {imgSrc, imgAlt, title, hypertext}
107
+ slides: { type: Array, required: true }, //Array of slides {imgSrc, imgAlt, title, hypertext}
108
+ name: { type: String, default: 'toto cool' }
93
109
  },
94
110
  data() {
95
111
  return {
96
- currentSlide: 1, //Slide management
112
+ currentSlide: null, //Slide management
97
113
  requiredProperties: ['imgSrc'], //For slides validation
98
- optionalProperties: ['title', 'imgAlt', 'hypertext'] //For slides validation
114
+ optionalProperties: ['title', 'imgAlt', 'hypertext'], //For slides validation
115
+ errorsSlider: []
99
116
  }
100
117
  },
101
118
  computed: {
@@ -106,26 +123,14 @@ export default {
106
123
  return !(this.currentSlide < this.slideLength)
107
124
  },
108
125
  slideLength() {
109
- return this.slides.length
126
+ return this.slides ? this.slides.length : 0
110
127
  }
111
128
  },
129
+ created() {
130
+ this.currentSlide = this.slides && this.slides.length ? 1 : 0
131
+ },
112
132
  mounted() {
113
- //Validating slides
114
- if (Array.isArray(this.slides) && this.slideLength > 0) {
115
- //Validate properties for all the slides
116
- for (const slide of this.slides) {
117
- this.validateProperties(
118
- this.requiredProperties,
119
- this.optionalProperties,
120
- slide
121
- )
122
- }
123
- } else {
124
- console.warn(
125
- `%c WARNING!>>> AppCompSlider : slides must be an array of at least one item`,
126
- 'background: orange; color: white; display: block; margin:5px;'
127
- )
128
- }
133
+ this.validateCarousel()
129
134
  },
130
135
  methods: {
131
136
  prevSlide() {
@@ -138,6 +143,27 @@ export default {
138
143
  this.currentSlide++
139
144
  }
140
145
  },
146
+ validateCarousel() {
147
+ //Validating slides
148
+
149
+ if (Array.isArray(this.slides) && this.slideLength > 0) {
150
+ //Validate properties for all the slides
151
+ for (const slide of this.slides) {
152
+ this.validateProperties(
153
+ this.requiredProperties,
154
+ this.optionalProperties,
155
+ slide
156
+ )
157
+ }
158
+ } else {
159
+ let msg = `Le corrousel doit avoit au moins un element.`
160
+ this.errorsSlider.push(msg)
161
+ console.warn(
162
+ `%c WARNING!>>> AppCompSlider : slides must be an array of at least one item`,
163
+ 'background: orange; color: white; display: block; margin:5px;'
164
+ )
165
+ }
166
+ },
141
167
  //Validate the properties of a specific object (currentObject)
142
168
  validateProperties(requiredProperties, optionalProperties, currentObject) {
143
169
  let allProperties = requiredProperties.concat(optionalProperties)
@@ -154,6 +180,8 @@ export default {
154
180
  `%c WARNING!>>> AppCompSlider : slides ${wrongProperties} invalid. Required properties: ${requiredProperties} . Optional properties: ${optionalProperties}`,
155
181
  'background: orange; color: white; display: block; margin:5px;'
156
182
  )
183
+ let msg = `Attribut(s) invalide(s): <i>${wrongProperties}</i> . Consultez la console pour plus de details.`
184
+ this.errorsSlider.push(msg)
157
185
  }
158
186
  //Validate if all required properties are present in currentObject
159
187
  if (missingRequired.length > 0) {
@@ -161,6 +189,8 @@ export default {
161
189
  `%c WARNING!>>> AppCompQuizSlider : slides missing required ${missingRequired} property. Required properties: ${requiredProperties} . Optional properties: ${optionalProperties}`,
162
190
  'background: orange; color: white; display: block; margin:5px;'
163
191
  )
192
+ let msg = `Une/certaines propriété(s) sont manquante(s). Consultez la console pour plus de details.`
193
+ this.errorsSlider.push(msg)
164
194
  }
165
195
  },
166
196
  //Get all the invalids properties from the object
@@ -166,6 +166,11 @@ fieldset {
166
166
  -webkit-box-shadow: inherit;
167
167
  }
168
168
 
169
+ fieldset{
170
+ border: inherit;
171
+ }
172
+
173
+
169
174
  .custom-checkbox {
170
175
  width: 100%;
171
176
  height: 100%;
@@ -22,9 +22,12 @@
22
22
  :id="inputDataId + '_' + singleDropdown.id"
23
23
  v-model="quizInputTypeValue[singleDropdown.id]"
24
24
  item-title="text"
25
+ :item-props="true"
25
26
  :items="singleDropdown.option"
26
27
  :disabled="quizLimitActive"
27
- />
28
+ :open-text="$t('message.dropdown_list') + ' ' + singleDropdown.ennonce"
29
+ close-text=""
30
+ ></v-select>
28
31
  </div>
29
32
  </div>
30
33
  </template>
@@ -124,17 +127,15 @@ export default {
124
127
  const defaultAnswer = {
125
128
  value: null,
126
129
  disabled: true,
127
- text: this.$t('message.first_option_dropdown')
130
+ text: this.$t('message.first_option_dropdown'),
131
+ selected: true
128
132
  }
129
133
  let selectedChoices = []
130
134
  for (let i = 0; i < this.inputData.length; i++) {
131
- let singleDropdown
132
- if (this.shuffleAnswers) {
133
- singleDropdown = this.inputData[i]
135
+ let singleDropdown = this.inputData[i]
136
+ if (this.shuffleAnswers)
134
137
  singleDropdown.option = this.shuffleArray(singleDropdown.option)
135
- } else {
136
- singleDropdown = this.inputData[i]
137
- }
138
+
138
139
  if (
139
140
  this.inputData[i].option[0].text !==
140
141
  this.$t('message.first_option_dropdown')
@@ -156,4 +157,45 @@ export default {
156
157
  .dropdown-container {
157
158
  position: relative;
158
159
  }
160
+
161
+ .texteatrou {
162
+ display: inline !important;
163
+ .cnt-input {
164
+ display: inline;
165
+
166
+ .v-input {
167
+ display: inline-block !important;
168
+
169
+ .v-input__control,
170
+ .v-field {
171
+ width: 240px !important;
172
+ grid-area: inherit !important;
173
+
174
+ .v-field__field {
175
+ height: 25px !important;
176
+ padding: 0 !important;
177
+ min-height: inherit !important;
178
+
179
+ .v-field__input {
180
+ padding-top: 0 !important;
181
+ padding-bottom: 0 !important;
182
+ min-height: inherit !important;
183
+
184
+ .v-select__selection {
185
+ min-height: inherit !important;
186
+ }
187
+ }
188
+ }
189
+
190
+ .v-field__append-inner {
191
+ margin-right: 6px;
192
+ }
193
+ }
194
+
195
+ .v-input__details {
196
+ display: none;
197
+ }
198
+ }
199
+ }
200
+ }
159
201
  </style>
@@ -100,7 +100,26 @@ export default {
100
100
  }
101
101
  }
102
102
  </script>
103
- <style>
104
- .box-reponse-ouverte {
103
+ <style lang="scss">
104
+ .quiz-texte-troue {
105
+ .texteatrou {
106
+ .v-input {
107
+ display: inline-block !important;
108
+ width: 150px;
109
+
110
+ .v-field {
111
+ height: 25px;
112
+
113
+ input {
114
+ padding: 0 24px;
115
+ min-height: 25px !important;
116
+ }
117
+ }
118
+
119
+ .v-input__details {
120
+ display: none;
121
+ }
122
+ }
123
+ }
105
124
  }
106
125
  </style>
@@ -101,6 +101,7 @@ export default {
101
101
  quizInputTypeValue: {
102
102
  deep: true,
103
103
  handler(newValue) {
104
+ // const val = newValue.length == 0 ? null : newValue
104
105
  this.$emit('input-change', newValue)
105
106
  }
106
107
  }
@@ -30,8 +30,11 @@
30
30
  item-title="text"
31
31
  class="dropdown"
32
32
  :items="singleDropdown.option"
33
+ :item-props="true"
33
34
  :disabled="quizLimitActive"
34
35
  :aria-describedby="`${inputDataId}_${singleDropdown.id}-msg-erreur`"
36
+ :open-text="$t('message.dropdown_list')"
37
+ close-text=""
35
38
  />
36
39
  </div>
37
40
 
@@ -163,16 +166,20 @@ export default {
163
166
  const defaultAnswer = {
164
167
  value: null,
165
168
  disabled: true,
169
+ selected: true,
166
170
  text: this.$t('message.first_option_dropdown')
167
171
  }
168
172
  let selectedChoices = []
173
+
169
174
  for (let i = 0; i < this.inputData.length; i++) {
170
175
  let singleDropdown = {}
171
176
  if (this.shuffleAnswers) {
172
177
  singleDropdown = this.inputData[i]
178
+
173
179
  singleDropdown[Object.keys(this.inputData[i])[0].toString()] =
174
180
  this.shuffleArray(Object.values(this.inputData[i])[0])
175
181
  } else {
182
+ // this.inputData[i].splice(0, 0, defaultAnswer)
176
183
  singleDropdown = this.inputData[i]
177
184
  }
178
185
  for (
@@ -188,6 +195,7 @@ export default {
188
195
  Object.values(singleDropdown)[0].splice(index, 1)
189
196
  }
190
197
  }
198
+
191
199
  Object.values(singleDropdown)[0].unshift(defaultAnswer)
192
200
 
193
201
  selectedChoices.push(null)