fcad-core-dragon 2.1.0-beta.1 → 2.1.0-beta.3

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.
@@ -430,6 +430,10 @@ mediaMixins is used for all the methods/data shared between audio and video. In
430
430
  </app-base-button>
431
431
  </div>
432
432
  </div>
433
+ <div v-if="appDebugMode" class="timer">
434
+ <!-- <div class="timer"> -->
435
+ {{ timer.getElapsedTime() }}
436
+ </div>
433
437
  </div>
434
438
  </div>
435
439
  </template>
@@ -438,21 +442,24 @@ mediaMixins is used for all the methods/data shared between audio and video. In
438
442
  import { mapState, mapActions } from 'pinia'
439
443
  import { useAppStore } from '../module/stores/appStore'
440
444
  import axios from 'axios'
441
-
445
+ import { Timer } from '../composables/useTimer'
446
+ import { reactive } from 'vue'
442
447
  export default {
443
448
  inject: ['userInteraction'],
444
449
  props: {
445
450
  mediaToPlay: { type: [Object, Boolean], default: false }
446
451
  },
447
-
448
452
  emits: ['resize-video'],
449
- setup() {
453
+
454
+ setup(props) {
450
455
  const store = useAppStore()
451
- return { store }
456
+ const id = `plyr_${props.mediaToPlay.id}`
457
+ const timer = reactive(new Timer(id)) // Making Timer instance reactive to be able to track changes
458
+
459
+ return { id, store, timer }
452
460
  },
453
461
  data() {
454
462
  return {
455
- id: `plyr_${this.mediaToPlay.id}`,
456
463
  //Playback animation
457
464
  playClicked: false,
458
465
  playBackAnim: true,
@@ -521,7 +528,14 @@ export default {
521
528
  progressBar: false,
522
529
  volumeSlider: false
523
530
  },
524
- otherVideoTranscriptShown: false
531
+ otherVideoTranscriptShown: false,
532
+ previousTime: 0,
533
+ viewedThreshold: 0.85,
534
+ viewedThresholdReached: false,
535
+ startedTime: 0,
536
+ playStmtSent: false,
537
+ mediaRawData: null
538
+
525
539
  //==========================================================
526
540
  }
527
541
  },
@@ -539,8 +553,12 @@ export default {
539
553
  'getMediaPlaybarValues',
540
554
  'getPageInteraction',
541
555
  'getUserInteraction',
542
- 'getMediaMuted'
556
+ 'getMediaMuted',
557
+ 'getAppDebugMode'
543
558
  ]),
559
+ appDebugMode() {
560
+ return this.getAppDebugMode
561
+ },
544
562
  //==========================================================
545
563
  //MediaElement
546
564
  mediaElement() {
@@ -636,7 +654,7 @@ export default {
636
654
 
637
655
  return txtA11Y
638
656
  },
639
- //==========================================================
657
+
640
658
  //Transcript
641
659
  hasTranscript() {
642
660
  if (!this.mediaToPlay) return
@@ -715,6 +733,18 @@ export default {
715
733
  },
716
734
  immediate: true
717
735
  // deep: true
736
+ },
737
+ getCurrentPage: {
738
+ handler(newValue) {
739
+ if (!newValue.audiosData && !newValue.videosData) return
740
+ const { audiosData, videosData } = this.getCurrentPage
741
+ this.mediaRawData =
742
+ this.mediaToPlay.mType == 'video'
743
+ ? videosData.find((el) => el.id == this.mediaToPlay.id)
744
+ : audiosData.find((el) => el.id == this.mediaToPlay.id)
745
+ },
746
+ immediate: true,
747
+ deep: true
718
748
  }
719
749
  },
720
750
 
@@ -792,7 +822,7 @@ export default {
792
822
  //window handlers for playbar progress
793
823
  window.addEventListener('mousemove', this.progressWindowMove)
794
824
  window.addEventListener('mouseup', this.progressWindowUp)
795
- //EndprogressBar
825
+
796
826
  //Update data when media as a timeupdate/ended
797
827
  this.mediaElement.addEventListener(
798
828
  'timeupdate',
@@ -835,22 +865,32 @@ export default {
835
865
  },
836
866
  /**
837
867
  * @description - play or pause the media
838
- * @fires manage-media-players - to the PAge the media in play
868
+ * when the media starts playing, send a statement to the LRS.
869
+ * Statement is sent only once per media play
870
+ * @fires manage-media-players - to the Page the media in play
839
871
  */
840
- togglePlay() {
872
+ async togglePlay() {
841
873
  //If the progressBar is at the end, restart the media
842
874
  if (this.progressBarEnded) {
843
875
  this.mediaElement.currentTime = 0
844
876
  this.currentTime = 0
845
877
  }
846
- //MediaElement
878
+ //Play or Pause the media
847
879
  this.isPlaying ? this.mediaElement.pause() : this.mediaElement.play()
880
+ this.startedTime = this.mediaElement.currentTime
881
+ // start or pause the timer depending of the state of the media
882
+ !this.isPlaying ? this.timer.start() : this.timer.pause()
883
+
884
+ if (!this.playStmtSent) {
885
+ this.sendMediaStatement('play', this.startedTime, null)
886
+ this.playStmtSent = true
887
+ }
848
888
  //Data
849
889
  if (!this.isPlaying) {
850
890
  //Signal to set this mediaElement as the last playing
851
891
  this.$bus.$emit('manage-media-players', this.mediaToPlay)
892
+ this.previousTime = Math.round(this.mediaElement.currentTime)
852
893
  }
853
-
854
894
  this.isPlaying = !this.isPlaying
855
895
  this.canReplay = false
856
896
  },
@@ -878,6 +918,15 @@ export default {
878
918
  if (this.mediaElement && this.isPlaying) {
879
919
  this.mediaElement.pause()
880
920
  this.isPlaying = false
921
+
922
+ const expectedViewTime = Number(
923
+ this.timer.getElapsedTime() / Math.trunc(this.mediaDuration)
924
+ ) //relation view time to media duration
925
+
926
+ this.timer.stop()
927
+
928
+ this.sendMediaStatement('end', this.startedTime, expectedViewTime)
929
+ this.playStmtSent = false //reset the play statement for next play
881
930
  }
882
931
  this.$bus.$emit('media-viewed', this.mediaToPlay.id)
883
932
  this.canReplay = true
@@ -1123,6 +1172,11 @@ export default {
1123
1172
  updateProgressBarTime(e) {
1124
1173
  //Get currentTime from the event
1125
1174
  this.currentTime = e.target.currentTime
1175
+
1176
+ //Set viewedThresholdReached to true when the threshold is reached
1177
+ if (Math.trunc(this.progressBarPercentage) == this.viewedThreshold * 100)
1178
+ this.viewedThresholdReached = true
1179
+
1126
1180
  //Update strings
1127
1181
  this.setProgressBarA11Y()
1128
1182
  //If replay is true and the progressIndicator is not at the end, set canReplay to false
@@ -1542,7 +1596,6 @@ export default {
1542
1596
  }
1543
1597
 
1544
1598
  if (this.transcriptEnabled && this.transcriptToShow) {
1545
- //this.$bus.$emit('resize-media', 'sm')
1546
1599
  //Resize video container
1547
1600
  this.$emit('resize-video', 'sm')
1548
1601
  //Open sidebar with the transcript
@@ -1555,7 +1608,6 @@ export default {
1555
1608
 
1556
1609
  // Send close signal for the side bar when transcipt state is not enabled
1557
1610
  if (!this.transcriptEnabled) {
1558
- //this.$bus.$emit('resize-media', 'lg')
1559
1611
  //Resize video container
1560
1612
  this.$emit('resize-video', 'lg')
1561
1613
  //Open sidebar with the transcript
@@ -1612,7 +1664,8 @@ export default {
1612
1664
  },
1613
1665
 
1614
1666
  /**
1615
- * @description Show the media controler */
1667
+ * @description Show the media controler
1668
+ */
1616
1669
  showControls() {
1617
1670
  this.showControlsValue = true
1618
1671
  if (this.hideTimer) clearTimeout(this.hideTimer) //cancel existing timer
@@ -1629,6 +1682,94 @@ export default {
1629
1682
  this.hideTimer = setTimeout(() => {
1630
1683
  this.showControlsValue = false
1631
1684
  }, this.delayUntilHide)
1685
+ },
1686
+
1687
+ /**
1688
+ * @description - Send xAPI statement for media play and end
1689
+ * @param {String} action - play or end
1690
+ * @param {Number} startTime - time in seconds when the media start to be played
1691
+ * @param {Number} endTime - time in seconds when the media stop to be played
1692
+ */
1693
+ async sendMediaStatement(action, startTime = null, endTime = null) {
1694
+ const expectedActions = ['play', 'end']
1695
+ if (!action || !expectedActions.includes(action)) return
1696
+
1697
+ // const { audiosData, videosData } = this.getCurrentPage
1698
+ const media = this.mediaRawData
1699
+ // this.mediaToPlay.mType == 'video'
1700
+ // ? videosData.find((el) => el.id == this.mediaToPlay.id)
1701
+ // : audiosData.find((el) => el.id == this.mediaToPlay.id)
1702
+
1703
+ if (!media || !media.mSources || !media.mSources.length) return
1704
+
1705
+ const { mTitle, mSources } = media
1706
+ const { mType } = this.mediaToPlay
1707
+
1708
+ const URIBase = `media/${mType}/`
1709
+
1710
+ const stmt = {
1711
+ id: (() => `${URIBase}${mSources[0].src.split('/').toReversed()[0]}`)(),
1712
+ verb: null,
1713
+ definition: mTitle || mSources[0].src,
1714
+ description: null,
1715
+ type: `https://w3id.org/xapi/${mType}/activity-type/${mType}`,
1716
+ context: {
1717
+ contextActivities: {
1718
+ category: [{ id: `https://w3id.org/xapi/${mType}` }]
1719
+ },
1720
+ extensions: {
1721
+ 'https://w3id.org/xapi/video/extensions/session-id':
1722
+ this.getModuleInfo.id
1723
+ }
1724
+ },
1725
+ result: null
1726
+ }
1727
+
1728
+ switch (action) {
1729
+ case 'play': {
1730
+ stmt.verb = 'played'
1731
+ stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
1732
+ stmt.result = {
1733
+ extensions: {
1734
+ 'https://w3id.org/xapi/video/extensions/time':
1735
+ Math.round(startTime),
1736
+ 'https://w3id.org/xapi/video/extensions/progress': Math.round(
1737
+ (startTime / this.mediaDuration) * 100
1738
+ )
1739
+ }
1740
+ }
1741
+ break
1742
+ }
1743
+
1744
+ case 'end': {
1745
+ stmt.verb = 'completed'
1746
+ stmt.description = `${mType} ${stmt.id.replace(URIBase, '')}`
1747
+ stmt.result = {
1748
+ extensions: {
1749
+ 'https://w3id.org/xapi/video/extensions/progress': endTime * 100,
1750
+ 'https://w3id.org/xapi/video/extensions/time-from':
1751
+ Math.round(startTime),
1752
+ 'https://w3id.org/xapi/video/extensions/time-to': this.currentTime
1753
+ },
1754
+ completion:
1755
+ endTime >= this.viewedThreshold && this.viewedThresholdReached
1756
+ }
1757
+ stmt.duration = this.timer.ISOTimeParser(endTime * this.mediaDuration)
1758
+ break
1759
+ }
1760
+ }
1761
+
1762
+ /* send play/end events to Google Analytics */
1763
+ const analyticsEventName = `fcad_${mType}_${action}`
1764
+ const analyticsEventParams = {
1765
+ title: stmt.definition,
1766
+ url: mSources[0].src
1767
+ }
1768
+ if (action === 'end') {
1769
+ analyticsEventParams.viewed = this.viewedThresholdReached
1770
+ }
1771
+ this.$analytics.sendEvent(analyticsEventName, analyticsEventParams)
1772
+ this.$bus.$emit('send-xapi-statement', stmt)
1632
1773
  }
1633
1774
  }
1634
1775
  }
@@ -43,7 +43,7 @@
43
43
  centered
44
44
  @after-leave="$close(pContent.cb_$close)"
45
45
  >
46
- <focus-trap v-model:active="dialogue">
46
+ <focus-trap v-model:active="dialogue" :prevent-scroll="true">
47
47
  <div class="pop-outside">
48
48
  <div class="pop-header">
49
49
  <app-base-button
@@ -85,7 +85,7 @@
85
85
  v-bind="pArgs"
86
86
  @after-leave="$close(pContent.cb_$close)"
87
87
  >
88
- <focus-trap v-model:active="dialogue">
88
+ <focus-trap v-model:active="dialogue" :prevent-scroll="true">
89
89
  <div class="pop-outside">
90
90
  <div class="pop-header">
91
91
  <app-base-button
@@ -192,7 +192,7 @@
192
192
  v-bind="pArgs"
193
193
  @after-leave="$close(pContent.cb_$close)"
194
194
  >
195
- <focus-trap v-model:active="dialogue">
195
+ <focus-trap v-model:active="dialogue" :prevent-scroll="true">
196
196
  <component :is="{ template: pContent.template }"></component>
197
197
  </focus-trap>
198
198
  </v-dialog>
@@ -58,7 +58,7 @@
58
58
  </div>
59
59
  </template>
60
60
  </v-col>
61
- <v-col cols="12" aria-live="polite">
61
+ <v-col cols="12" class="ctn-retro" aria-live="polite">
62
62
  <transition name="fade" mode="in-out">
63
63
  <div
64
64
  v-if="showRetro"
@@ -19,7 +19,7 @@
19
19
  v-if="quizRecall.title"
20
20
  class="quizRecall-title"
21
21
  >
22
- {{ quizRecall.title }} -
22
+ {{ quizRecall.title }}
23
23
  </component>
24
24
  <!--Quiz answer conditionning-->
25
25
  <app-base-error-display
@@ -60,7 +60,6 @@
60
60
  //Recall mixins has the necessary datas and functions to give back quizRecall statu
61
61
  import { mapState } from 'pinia'
62
62
  import { useAppStore } from '../module/stores/appStore'
63
- import { nextTick } from 'vue'
64
63
 
65
64
  export default {
66
65
  name: 'AppCompQuizRecall',
@@ -198,7 +197,7 @@ export default {
198
197
  },
199
198
  //Get datas from quizRecallData and userData and add them to quizRecall object
200
199
  async getQuizRecallAnswer(userData) {
201
- await nextTick() //wait for the DOM to update
200
+ await this.$nextTick() //wait for the DOM to update
202
201
  const { quizId, hypertext_done, hypertext_undone, title, titletag } =
203
202
  this.quizRecallData
204
203
 
@@ -9,7 +9,7 @@
9
9
 
10
10
  <template>
11
11
  <div v-if="error" id="sidebar-submenu" :class="{ isOpen: isOpened }">
12
- <focus-trap :active="isOpened">
12
+ <focus-trap :active="isOpened" :prevent-scroll="true">
13
13
  <div ref="target">
14
14
  <!-- <div class="submenu-header"> -->
15
15
  <app-base-button
@@ -0,0 +1,91 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
2
+ import { Timer } from '../../composables/useTimer'
3
+
4
+ // vitest vi utility ref: https://vitest.dev/api/vi.html
5
+ // vi fakeTimers ref: https://vitest.dev/api/vi.html#vi-usefaketimers
6
+
7
+ describe('Timer class', () => {
8
+ let timer
9
+
10
+ beforeEach(() => {
11
+ Timer.timers.clear() // Clear existing timers before each test
12
+ timer = new Timer()
13
+ vi.useFakeTimers() // Enable fake timers before each test
14
+ })
15
+
16
+ afterEach(() => {
17
+ timer.destroy() // destroy created timer after each test
18
+ vi.useRealTimers() // Restore real timers after each test
19
+ })
20
+
21
+ it('should create a Timer instance with a unique ID', () => {
22
+ const timer2 = new Timer() //create a second instance
23
+ expect(timer2.getTimerID()).not.toBe(timer.getTimerID())
24
+ expect(Timer.timers.size).toBe(2) // Two timers should be registered
25
+ })
26
+
27
+ it('should return a timer instance by it ID', () => {
28
+ const timer2 = new Timer('timer22') //create a second instance with custom ID
29
+ expect(timer.getTimer('timer22')).toBeInstanceOf(Timer)
30
+ })
31
+
32
+ it('should throw error when a timer instance doesn`t exist', () => {
33
+ const id = 'noExistingTimer'
34
+ expect(() => timer.getTimer(id)).toThrowError(
35
+ `Timer with ID ${id} does not exist.`
36
+ )
37
+ })
38
+
39
+ it('should initialize timer at 0 seconds', () => {
40
+ expect(timer.getTime()).toBe(0)
41
+ })
42
+
43
+ it('should start the timer and increment time every second', () => {
44
+ timer.start()
45
+ expect(timer.getTimerState()).toBe('started')
46
+
47
+ vi.advanceTimersByTime(5000) // Fast-forward 3 seconds
48
+ expect(timer.getTime()).toBe(5) // Should be approximately 5 seconds
49
+ })
50
+
51
+ it('should display time in correct format when converting to ISOformats', () => {
52
+ timer.start()
53
+ expect(timer.getTimerState()).toBe('started')
54
+
55
+ vi.advanceTimersByTime(65000) // Fast-forward 3 seconds
56
+
57
+ const fullISOFormat = '1970-01-01T00:01:05.000Z'
58
+ const isoTimeStr = '00:01:05'
59
+
60
+ expect(timer.getTime()).toBe(65) // Should be approximately 1 min 5 seconds (00:01:05)
61
+ expect(timer.formatToISOString(timer.getTime())).toBe(fullISOFormat)
62
+ expect(timer.ISOTimeParser(timer.getTime())).toBe(isoTimeStr)
63
+ })
64
+
65
+ it('should pause the timer', () => {
66
+ timer.start()
67
+ vi.advanceTimersByTime(2000)
68
+ timer.pause()
69
+ const current = timer.getTime()
70
+
71
+ // Advance time but timer should stay paused
72
+ vi.advanceTimersByTime(3000)
73
+ expect(timer.getTime()).toBe(current) // about 2 seconds
74
+ expect(timer.ISOTimeParser(timer.getTime())).toBe('00:00:02')
75
+ })
76
+
77
+ it('should reset the timer to 0 when timer stops', () => {
78
+ timer.start()
79
+ vi.advanceTimersByTime(4000)
80
+ timer.stop()
81
+ expect(timer.getTime()).toBe(0)
82
+ expect(timer.getTimerState()).toBe('stopped')
83
+ })
84
+
85
+ it('should destroy timer', () => {
86
+ const id = timer.getTimerID()
87
+ timer.destroy()
88
+ expect(Timer.timers.get(id)).toBeUndefined()
89
+ expect(Timer.timers.size).toBe(0)
90
+ })
91
+ })
@@ -0,0 +1,56 @@
1
+ export class IdleDetector {
2
+ constructor() {
3
+ this.idleCounter = 0
4
+ this.idleTimeoutID = null
5
+ this.detectorState = 'stopped' // can be 'pause' or 'stop'
6
+ this.idleTimer = null
7
+ }
8
+
9
+ /**
10
+ * @description Start the idle timer -
11
+ * creates an interval to track idle time and a timeout to trigger actions
12
+ * after a specified period of inactivity.
13
+ * @param {Function} action - a callback function to execute when the idle timeout is reached.
14
+ * @param {Number} timeout - the duration in milliseconds before the action is triggered.
15
+ */
16
+ startIdleTimer(action = null, timeout = 0) {
17
+ this.idleTimer = setInterval(() => {
18
+ this.idleCounter += 1
19
+ }, 1000)
20
+ this.detectorState = 'started'
21
+
22
+ if (!action || typeof action !== 'function') return
23
+
24
+ this.idleTimeoutID = setTimeout(() => action(), timeout)
25
+ }
26
+
27
+ /**
28
+ * @description Reset the idle timer -
29
+ * clears the current idle timer and timeout,
30
+ */
31
+ stopIdleTimer() {
32
+ this.idleCounter = 0
33
+ clearInterval(this.idleTimer)
34
+ clearTimeout(this.idleTimeoutID)
35
+ this.detectorState = 'stopped'
36
+ }
37
+
38
+ /**
39
+ * @description Get the current state of the timer
40
+ *
41
+ * @returns {String} - timer state, can be 'started' or 'stopped'
42
+ */
43
+ getDectorState() {
44
+ return this.detectorState
45
+ }
46
+
47
+ /**
48
+ * @description Get the elapsed time for the idle timer.
49
+ * This is the time the idle detector has been running since it was started.
50
+ *
51
+ * @returns {Number} - the time count in seconds
52
+ */
53
+ getElapsedTime() {
54
+ return this.idleCounter
55
+ }
56
+ }
@@ -1,4 +1,4 @@
1
- //This composable sould Extend the functionality of a the quiz
1
+ //This composable should Extend the functionality of a the quiz
2
2
 
3
3
  import i18n from '@/i18n' //Must import directly the local from project app because vue-18in does not work in legacy mode with composable
4
4
 
@@ -0,0 +1,175 @@
1
+ export class Timer {
2
+ static timers = new Map() // Store all timers by ID
3
+
4
+ constructor(timerID) {
5
+ this.timerID = timerID || this.createTimerID() //timerID of the timer
6
+ this.timeCounter = 0 //time counter
7
+ this.elapsedCounter = 0 //elapsed time counter how lofg the timer is running
8
+ this.interval = null //interval for the timer
9
+ this.timerState = 'stopped' //state of the timer, can be 'started' or 'stopped'
10
+ Timer.timers.set(this.timerID, this) // Store the timer in the static map
11
+ }
12
+
13
+ /**
14
+ * @description Start the timer
15
+ */
16
+ start() {
17
+ if (this.getTimerState() == 'stopped') {
18
+ this.interval = setInterval(() => {
19
+ this.timeCounter += 1
20
+ this.elapsedCounter += 1
21
+ }, 1000)
22
+ this.timerState = 'started'
23
+
24
+ }
25
+ }
26
+
27
+ /** @description Pause the timer */
28
+ pause() {
29
+ if (this.getTimerState() == 'started') {
30
+ clearInterval(this.interval)
31
+ this.timerState = 'stopped'
32
+ }
33
+ }
34
+
35
+ /** @description Stop the timer and reset the timer to zero */
36
+ stop() {
37
+ if (this.getTimerState() == 'started') {
38
+ clearInterval(this.interval)
39
+ this.timeCounter = 0
40
+ this.elapsedCounter = 0
41
+ this.timerState = 'stopped'
42
+
43
+ }
44
+ }
45
+
46
+ /** @description Destroy the timer instance */
47
+ destroy() {
48
+ this.pause()
49
+ Timer.timers.delete(this.timerID)
50
+ }
51
+
52
+ /** @description Get the timer ID */
53
+ getTimerID() {
54
+ return this.timerID
55
+ }
56
+
57
+ /**
58
+ *
59
+ * @description retrive a timer instance by its ID
60
+ * If no ID is provided, it returns the current timer instance.
61
+ * @param {String} id - the ID of the timer to retrieve
62
+ * If no ID is provided, it returns the current timer instance.
63
+ *
64
+ * @throws {Error} - if the timer with the specified ID does not exist
65
+ *
66
+ * @returns {Timer} - the timer instance
67
+ */
68
+ getTimer(id=null) {
69
+ if (!id) {
70
+ console.warn('No timer ID provided. Returning the current timer.')
71
+ return this
72
+ }
73
+ if (!Timer.timers.has(id)) throw new Error(`Timer with ID ${id} does not exist.`)
74
+ return Timer.timers.get(id)
75
+ }
76
+
77
+ /**
78
+ * @description Get the current time counter in seconds
79
+ *
80
+ * @returns {Number} - the time counter in seconds
81
+ */
82
+ getTime() {
83
+ return this.timeCounter
84
+ }
85
+ /**
86
+ * @description Get the current state of the timer
87
+ *
88
+ * @returns {String} - timer state, can be 'started' or 'stopped'
89
+ */
90
+ getTimerState() {
91
+ return this.timerState
92
+ }
93
+ /**
94
+ * @description Get the elapsed time for the timer
95
+ * This is the time the timer has been running since it was started.
96
+ * It is not reset when the timer is paused.
97
+ *
98
+ * @returns {Number} - the time count in seconds
99
+ */
100
+ getElapsedTime() {
101
+ return this.elapsedCounter
102
+ }
103
+
104
+
105
+ /**
106
+ * @description Get all existing timers
107
+ *
108
+ * @returns {Map} - a map of all timer instances with their IDs as keys
109
+ */
110
+
111
+ static getAllTimers() {
112
+ return Timer.timers
113
+ }
114
+
115
+ /**
116
+ * @description - This method generates a unique timer ID
117
+ *
118
+ * @returns {String} - a unique timer ID
119
+ */
120
+ createTimerID() {
121
+ const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
122
+ let str = ''
123
+ const n = 2
124
+ for (let i = 0; i < n; i++) {
125
+ str = `${str}${alphabet[Math.floor(Math.random() * alphabet.length)]}`
126
+ }
127
+
128
+ const id = `Tm$${str}${Math.floor(Math.random() * 1000)}`
129
+
130
+ return id
131
+ }
132
+
133
+
134
+ /**
135
+ * @description Format seconds to ISO 8601 format
136
+ * Example of ISO 8601 time format of 24 chars: "1970-01-04T14:50:00.000Z"
137
+ * @param {Number} seconds - time in seconds
138
+ *
139
+ * @returns {String} - ISO 8601 formatted time string // YYYY-MM-DDTHH:mm:ss.sssZ
140
+ */
141
+ formatToISOString(seconds) {
142
+ let d = new Date(null) //create a default date ref
143
+ d.setSeconds(seconds) //set the time with passed numbers of seconds
144
+
145
+ let ISOTime = d.toISOString()
146
+
147
+ return ISOTime
148
+ }
149
+
150
+ /**
151
+ * @description gives time period in the format of HH:mm:ss
152
+ * @param {Number} seconds - time in seconds
153
+ *
154
+ * @returns {String} - time period in the format of HH:mm:ss
155
+ */
156
+ ISOTimeParser(seconds) {
157
+ let ISOTimePeriod = this.formatToISOString(seconds).substring(8, 19) // only the time portion of it
158
+ ISOTimePeriod = ISOTimePeriod.split('T')
159
+ const DDToHrs = (parseInt(ISOTimePeriod[0]) - 1) * 24 // convert xxT to hrs
160
+ const periodOfTime = ISOTimePeriod[1] ? ISOTimePeriod[1].split(':') : null
161
+
162
+ if (!periodOfTime) return '00:00:00'
163
+
164
+ let timeString = ''
165
+ let HH = (DDToHrs + parseInt(periodOfTime[0])).toString()
166
+ let mm = periodOfTime[1]
167
+ let ss = periodOfTime[2]
168
+
169
+ if (HH.length === 1) HH = `0${HH}`
170
+
171
+ timeString = `${HH}:${mm}:${ss}`
172
+
173
+ return timeString
174
+ }
175
+ }