@knowark/componarkjs 1.7.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.
Files changed (116) hide show
  1. package/Makefile +49 -0
  2. package/README.md +47 -0
  3. package/knowarkjs.code-workspace +29 -0
  4. package/lib/base/component/README.rst +113 -0
  5. package/lib/base/component/component.js +115 -0
  6. package/lib/base/component/component.test.js +327 -0
  7. package/lib/base/component/index.js +3 -0
  8. package/lib/base/index.js +1 -0
  9. package/lib/base/styles/index.js +3 -0
  10. package/lib/base/styles/styles.js +320 -0
  11. package/lib/base/utils/define.js +21 -0
  12. package/lib/base/utils/define.test.js +62 -0
  13. package/lib/base/utils/format.js +24 -0
  14. package/lib/base/utils/format.test.js +19 -0
  15. package/lib/base/utils/helpers.js +96 -0
  16. package/lib/base/utils/helpers.test.js +154 -0
  17. package/lib/base/utils/index.js +5 -0
  18. package/lib/base/utils/slots.js +18 -0
  19. package/lib/base/utils/slots.test.js +52 -0
  20. package/lib/base/utils/uuid.js +9 -0
  21. package/lib/base/utils/uuid.test.js +19 -0
  22. package/lib/components/audio/README.md +22 -0
  23. package/lib/components/audio/components/audio.js +103 -0
  24. package/lib/components/audio/components/audio.test.js +127 -0
  25. package/lib/components/audio/index.js +1 -0
  26. package/lib/components/audio/styles/ark.css.js +83 -0
  27. package/lib/components/audio/styles/index.js +2 -0
  28. package/lib/components/camera/README.md +64 -0
  29. package/lib/components/camera/components/camera.js +85 -0
  30. package/lib/components/camera/components/camera.test.js +104 -0
  31. package/lib/components/camera/index.js +1 -0
  32. package/lib/components/camera/styles/ark.css.js +17 -0
  33. package/lib/components/camera/styles/index.js +2 -0
  34. package/lib/components/capture/components/capture.js +54 -0
  35. package/lib/components/capture/components/capture.test.js +112 -0
  36. package/lib/components/capture/index.js +1 -0
  37. package/lib/components/droparea/README.md +51 -0
  38. package/lib/components/droparea/components/droparea-preview.js +159 -0
  39. package/lib/components/droparea/components/droparea-preview.test.js +105 -0
  40. package/lib/components/droparea/components/droparea.js +165 -0
  41. package/lib/components/droparea/components/droparea.test.js +320 -0
  42. package/lib/components/droparea/index.js +1 -0
  43. package/lib/components/droparea/styles/ark.css.js +235 -0
  44. package/lib/components/droparea/styles/index.js +3 -0
  45. package/lib/components/emit/components/emit.js +33 -0
  46. package/lib/components/emit/components/emit.test.js +138 -0
  47. package/lib/components/emit/index.js +1 -0
  48. package/lib/components/index.js +9 -0
  49. package/lib/components/list/README.md +103 -0
  50. package/lib/components/list/components/item.test.js +93 -0
  51. package/lib/components/list/components/list.item.js +22 -0
  52. package/lib/components/list/components/list.js +96 -0
  53. package/lib/components/list/components/list.test.js +267 -0
  54. package/lib/components/list/index.js +2 -0
  55. package/lib/components/paginator/README.md +32 -0
  56. package/lib/components/paginator/components/paginator.js +110 -0
  57. package/lib/components/paginator/components/paginator.test.js +131 -0
  58. package/lib/components/paginator/index.js +1 -0
  59. package/lib/components/paginator/styles/ark.css.js +196 -0
  60. package/lib/components/paginator/styles/index.js +2 -0
  61. package/lib/components/spinner/README.md +41 -0
  62. package/lib/components/spinner/components/spinner.js +105 -0
  63. package/lib/components/spinner/components/spinner.test.js +50 -0
  64. package/lib/components/spinner/index.js +1 -0
  65. package/lib/components/spinner/styles/ark.css.js +658 -0
  66. package/lib/components/spinner/styles/index.js +2 -0
  67. package/lib/components/splitview/README.md +63 -0
  68. package/lib/components/splitview/components/splitview.detail.js +46 -0
  69. package/lib/components/splitview/components/splitview.detail.test.js +92 -0
  70. package/lib/components/splitview/components/splitview.js +69 -0
  71. package/lib/components/splitview/components/splitview.master.js +26 -0
  72. package/lib/components/splitview/components/splitview.master.test.js +55 -0
  73. package/lib/components/splitview/components/splitview.test.js +76 -0
  74. package/lib/components/splitview/index.js +3 -0
  75. package/lib/components/translate/README.md +56 -0
  76. package/lib/components/translate/components/translate.js +100 -0
  77. package/lib/components/translate/components/translate.test.js +226 -0
  78. package/lib/components/translate/index.js +1 -0
  79. package/lib/index.js +2 -0
  80. package/package.json +68 -0
  81. package/showcase/design/.htaccess +8 -0
  82. package/showcase/design/core/factories/development/development.factory.js +5 -0
  83. package/showcase/design/core/factories/development/index.js +1 -0
  84. package/showcase/design/core/factories/index.js +11 -0
  85. package/showcase/design/core/factories/standard.factory.js +19 -0
  86. package/showcase/design/index.html +22 -0
  87. package/showcase/design/index.js +7 -0
  88. package/showcase/design/screens/base/audio/audioDemo.js +32 -0
  89. package/showcase/design/screens/base/audio/index.js +25 -0
  90. package/showcase/design/screens/base/camera/cameraDemo.js +83 -0
  91. package/showcase/design/screens/base/camera/index.js +25 -0
  92. package/showcase/design/screens/base/droparea/dropareaDemo.js +88 -0
  93. package/showcase/design/screens/base/droparea/index.js +25 -0
  94. package/showcase/design/screens/base/index.js +42 -0
  95. package/showcase/design/screens/base/list/index.js +25 -0
  96. package/showcase/design/screens/base/list/listDemo.js +89 -0
  97. package/showcase/design/screens/base/paginator/index.js +25 -0
  98. package/showcase/design/screens/base/paginator/paginatorDemo.js +82 -0
  99. package/showcase/design/screens/base/root.component.js +294 -0
  100. package/showcase/design/screens/base/root.routes.js +28 -0
  101. package/showcase/design/screens/base/spinner/index.js +25 -0
  102. package/showcase/design/screens/base/spinner/spinnerDemo.js +55 -0
  103. package/showcase/design/screens/base/splitview/detailDemo.js +40 -0
  104. package/showcase/design/screens/base/splitview/index.js +25 -0
  105. package/showcase/design/screens/base/splitview/splitViewDemo.js +58 -0
  106. package/showcase/design/screens/base/translate/index.js +20 -0
  107. package/showcase/design/screens/base/translate/translateDemo.js +43 -0
  108. package/showcase/design/screens/main.js +12 -0
  109. package/showcase/design/screens/screens.routes.js +23 -0
  110. package/showcase/index.html +86 -0
  111. package/showcase/index.js +5 -0
  112. package/showcase/locales/en/default.json +5 -0
  113. package/showcase/locales/es/default.json +5 -0
  114. package/showcase/locales/fr/default.json +5 -0
  115. package/tsconfig.json +23 -0
  116. package/webpack.config.cjs +118 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @param {HTMLElement} container
3
+ * @return {Object<string, Array<HTMLElement>>}
4
+ * */
5
+ export function getSlots (container) {
6
+ const slots = { general: [] }
7
+
8
+ for (const element of container.children) {
9
+ const slot = element.getAttribute('slot') || 'general'
10
+ if (!Object.keys(slots).includes(slot)) slots[slot] = []
11
+
12
+ slots[slot].push(element)
13
+ }
14
+
15
+ return slots
16
+ }
17
+
18
+ export const slot = getSlots
@@ -0,0 +1,52 @@
1
+ import { getSlots } from './slots.js'
2
+
3
+ describe('Slots', () => {
4
+ it('can be rendered without content', () => {
5
+ const item = document.createElement('div')
6
+ const slots = getSlots(item)
7
+ expect(!slots.general.length).toBeTruthy()
8
+ })
9
+
10
+ it('can be rendered with content', () => {
11
+ const item = document.createElement('div')
12
+
13
+ const obj = document.createElement('div')
14
+
15
+ item.appendChild(obj)
16
+ const slots = getSlots(item)
17
+ expect(slots.general.length).toBeTruthy()
18
+ })
19
+
20
+ it('can be rendered with value slot', () => {
21
+ const item = document.createElement('div')
22
+
23
+ const obj = document.createElement('div')
24
+ const att = document.createAttribute('slot')
25
+ att.value = 'mySlot'
26
+ obj.setAttributeNode(att)
27
+
28
+ item.appendChild(obj)
29
+ const slots = getSlots(item)
30
+ expect(slots.mySlot.length).toBeTruthy()
31
+ })
32
+
33
+ it('can be rendered with multiple values slot', () => {
34
+ const item = document.createElement('div')
35
+
36
+ const obj = document.createElement('div')
37
+ const att = document.createAttribute('slot')
38
+ att.value = 'mySlot'
39
+ obj.setAttributeNode(att)
40
+
41
+ item.appendChild(obj)
42
+
43
+ const obj2 = document.createElement('div')
44
+ const att2 = document.createAttribute('slot')
45
+ att2.value = 'mySlot'
46
+ obj2.setAttributeNode(att2)
47
+
48
+ item.appendChild(obj2)
49
+ const slots = getSlots(item)
50
+ expect(slots.mySlot.length).toBeTruthy()
51
+ })
52
+ })
@@ -0,0 +1,9 @@
1
+ /** @returns {string} */
2
+ export function uuid () {
3
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
4
+ /[xy]/g, function (c) {
5
+ var r = (Math.random() * 16) | 0
6
+ var v = c === 'x' ? r : (r & 0x3) | 0x8
7
+ return v.toString(16)
8
+ })
9
+ }
@@ -0,0 +1,19 @@
1
+ import { uuid } from './uuid.js'
2
+
3
+ describe('Slots', () => {
4
+ it('groups separated by dashes', () => {
5
+ const id = uuid()
6
+ expect(id.split('-').length === 5).toBeTruthy()
7
+ })
8
+
9
+ it('total digits == 32', () => {
10
+ const id = uuid()
11
+
12
+ let digits = ''
13
+ id.split('-').forEach(element => {
14
+ digits += element
15
+ })
16
+
17
+ expect(digits.length === 32).toBeTruthy()
18
+ })
19
+ })
@@ -0,0 +1,22 @@
1
+ Audio
2
+ =====
3
+
4
+ `ark-audio` allows the user to record audio using the current microphone device.
5
+
6
+ ## Example
7
+
8
+ ``` html
9
+ <ark-audio></ark-audio>
10
+ ```
11
+
12
+ ## Attributes
13
+
14
+ | Name | Default | Description |
15
+ | :----: | :-----: | :--------------------------------------------------------: |
16
+ | status | - | Different status (idle,recording,done) adds automatically |
17
+
18
+ ## Properties
19
+
20
+ | Name | Default | Description |
21
+ | :----: | :-----: | :--------------------------------------------------------: |
22
+ | status | - | Different status (idle,recording,done) adds automatically |
@@ -0,0 +1,103 @@
1
+ import { Component } from '../../../base/component/index.js'
2
+ import styles from '../styles/index.js'
3
+
4
+ const tag = 'ark-audio'
5
+ export class Audio extends Component {
6
+ init (context = {}) {
7
+ this.status = 'idle'
8
+ this.dataURL = null
9
+ this.timerId = null
10
+ this.recorder = null
11
+ this.global = context.global || window
12
+
13
+ return super.init()
14
+ }
15
+
16
+ reflectedProperties() {
17
+ return ['status']
18
+ }
19
+
20
+ render () {
21
+ if (this.status === 'done') {
22
+ this.content = `
23
+ <div class="ark-audio__done">
24
+ <audio class="ark-audio__audio" controls></audio>
25
+ <button listen on-click="reset">❌</button>
26
+ </div>
27
+ `
28
+ } else if (this.status === 'recording') {
29
+ this.content = `
30
+ <div class="ark-audio__recording">
31
+ <label>Recording</label>
32
+ <span class="ark-audio__timer">00:00</span>
33
+ <button listen on-click="stop">⏹️</button>
34
+ </div>
35
+ `
36
+ } else {
37
+ this.content = `
38
+ <div class="ark-audio__idle">
39
+ <button listen on-click="start">⏺️</button>
40
+ </div>
41
+ `
42
+ }
43
+
44
+ return super.render()
45
+ }
46
+
47
+ /** @param {Event} event */
48
+ async start (event) {
49
+ event.stopPropagation()
50
+ this.status = 'recording'
51
+ this.render()
52
+ const options = {audio: true}
53
+ const navigator = this.global.navigator
54
+ const stream = await navigator.mediaDevices.getUserMedia(options)
55
+
56
+ this.recorder = new this.global.MediaRecorder(stream)
57
+ this.recorder.addEventListener('dataavailable', this._onData.bind(this))
58
+ this.timerId = this._time()
59
+ this.recorder.start()
60
+ }
61
+
62
+ /** @param {Event} event */
63
+ stop (event) {
64
+ event.stopPropagation()
65
+ this.status = 'done'
66
+ clearInterval(this.timerId)
67
+ this.recorder.stop()
68
+ this.recorder.stream.getTracks().forEach(track => track.stop())
69
+ this.render()
70
+ }
71
+
72
+ reset () {
73
+ this.status = 'idle'
74
+ this.render()
75
+ this.dataURL = null
76
+ this.timerId = null
77
+ this.recorder = null
78
+ }
79
+
80
+ _time () {
81
+ let count = 0
82
+ return setInterval(() => {
83
+ count += 1
84
+ const seconds = count % 60
85
+ const minutes = Math.trunc(count / 60)
86
+ let content = `${minutes < 10 ? '0' + minutes : minutes}:`
87
+ content += `${seconds < 10 ? '0' + seconds : seconds}`
88
+
89
+ const timer = this.select('.ark-audio__timer')
90
+ timer.textContent = content
91
+ }, 1000)
92
+ }
93
+
94
+ /** @param {any} event */
95
+ _onData(event) {
96
+ const audio = this.select('.ark-audio__audio')
97
+ audio['src'] = this.global.URL.createObjectURL(event.data)
98
+ const reader = new this.global.FileReader()
99
+ reader.readAsDataURL(event.data)
100
+ reader.onloadend = () => { this.dataURL = reader.result }
101
+ }
102
+ }
103
+ Component.define(tag, Audio, styles)
@@ -0,0 +1,127 @@
1
+ import { jest } from '@jest/globals'
2
+ import './audio.js'
3
+
4
+ jest.useFakeTimers()
5
+
6
+ const mockGlobal = {
7
+ navigator: {
8
+ mediaDevices: {
9
+ getUserMedia: async (options) => {}
10
+ }
11
+ },
12
+ MediaRecorder: function (stream) {
13
+ this.stream = stream
14
+ this.start = () => {}
15
+ this.addEventListener = (type, callback) => {}
16
+ this.stop = () => {}
17
+ this.stream = {getTracks: () => [{stop: () => null}]}
18
+ },
19
+ FileReader: function () {
20
+ const self = this
21
+ this.readAsDataURL = (data) => setTimeout(
22
+ () => self['onloadend'](), 1000)
23
+ this.result = 'base64::data::result'
24
+
25
+ },
26
+ URL: {createObjectURL: (data) => 'mock://data/url'}
27
+ }
28
+
29
+ describe('Audio', () => {
30
+ let container = null
31
+ beforeEach(() => {
32
+ container = document.createElement('div')
33
+ document.body.appendChild(container)
34
+ })
35
+
36
+ afterEach(() => {
37
+ container.remove()
38
+ container = null
39
+ })
40
+
41
+ it('can be instantiated', () => {
42
+ container.innerHTML = `
43
+ <ark-audio></ark-audio>
44
+ `
45
+ const audio = container.querySelector('ark-audio')
46
+ audio.init()
47
+ expect(audio).toBeTruthy()
48
+ })
49
+
50
+ it('can start recording', async () => {
51
+ container.innerHTML = `
52
+ <ark-audio></ark-audio>
53
+ `
54
+ const audio = container.querySelector('ark-audio')
55
+ audio.init({global: mockGlobal})
56
+
57
+ expect(audio.status).toEqual('idle')
58
+ await audio.start(new Event('click'))
59
+ expect(audio.status).toEqual('recording')
60
+ expect(audio.recorder).toBeTruthy()
61
+ })
62
+
63
+ it('can stop recording', async () => {
64
+ container.innerHTML = `
65
+ <ark-audio></ark-audio>
66
+ `
67
+ const audio = container.querySelector('ark-audio')
68
+ audio.init({global: mockGlobal})
69
+
70
+ expect(audio.status).toEqual('idle')
71
+ await audio.start(new Event('click'))
72
+ expect(audio.status).toEqual('recording')
73
+ audio.stop(new Event('click'))
74
+ expect(audio.status).toEqual('done')
75
+ })
76
+
77
+ it('can reset recording', async () => {
78
+ container.innerHTML = `
79
+ <ark-audio></ark-audio>
80
+ `
81
+ const audio = container.querySelector('ark-audio')
82
+ audio.init({global: mockGlobal})
83
+
84
+ expect(audio.status).toEqual('idle')
85
+ await audio.start(new Event('click'))
86
+ expect(audio.status).toEqual('recording')
87
+ audio.stop(new Event('click'))
88
+ expect(audio.status).toEqual('done')
89
+ audio.reset(new Event('click'))
90
+ expect(audio.status).toEqual('idle')
91
+ expect(audio.recorder).toBeNull()
92
+ })
93
+
94
+ it('counts the ellapsed time of the recording', async () => {
95
+ container.innerHTML = `
96
+ <ark-audio></ark-audio>
97
+ `
98
+ const audio = container.querySelector('ark-audio')
99
+ audio.init({global: mockGlobal})
100
+
101
+ await audio.start(new Event('click'))
102
+ jest.runOnlyPendingTimers()
103
+
104
+ const timer = audio.select('.ark-audio__timer')
105
+ expect(timer.textContent).toEqual('00:01')
106
+ jest.advanceTimersByTime(623000)
107
+ expect(timer.textContent).toEqual('10:24')
108
+ })
109
+
110
+ it('sets the dataURL (base64) property when stopped', async () => {
111
+ container.innerHTML = `
112
+ <ark-audio></ark-audio>
113
+ `
114
+ const audio = container.querySelector('ark-audio')
115
+ audio.init({global: mockGlobal})
116
+
117
+ await audio.start(new Event('click'))
118
+ audio.stop(new Event('click'))
119
+
120
+ audio._onData({data: new Blob(['Hello'], {type: 'text/plain'})})
121
+ jest.runOnlyPendingTimers()
122
+
123
+ expect(audio.dataURL).toEqual('base64::data::result')
124
+ expect(audio.querySelector('.ark-audio__audio').src).toEqual(
125
+ 'mock://data/url')
126
+ })
127
+ })
@@ -0,0 +1 @@
1
+ export { Audio } from './components/audio.js'
@@ -0,0 +1,83 @@
1
+ const css = String.raw; export default css`
2
+ .ark-audio {
3
+ display: grid;
4
+ align-items: center;
5
+ width: fit-content;
6
+ height: auto;
7
+ overflow: hidden;
8
+ padding: 0 0.5rem;
9
+ user-select: none;
10
+ background: white;
11
+ border: 2px solid rgba(190, 189, 189, 0.856);
12
+ min-width: 150px;
13
+ }
14
+
15
+ .ark-audio ark-button {
16
+ border: 0;
17
+ outline: 0;
18
+ background: transparent;
19
+ text-align: center;
20
+ margin: 0;
21
+ padding: 0;
22
+ }
23
+
24
+ .ark-audio__recording {
25
+ display: grid;
26
+ grid-template-columns: repeat(3, 1fr);
27
+ justify-items: center;
28
+ align-items: center;
29
+ width: 15rem;
30
+ }
31
+
32
+ .ark-audio__recording label {
33
+ font-weight: 700;
34
+ }
35
+
36
+ .ark-audio__timer {
37
+ display: grid;
38
+ justify-items: center;
39
+ background: var(--light);
40
+ color: var(--dark);
41
+ align-items: center;
42
+ padding: 0.8rem 0.5rem;
43
+ }
44
+
45
+ .ark-audio__idle {
46
+ display: grid;
47
+ }
48
+
49
+ .ark-audio__idle::before {
50
+ content: "Grabar";
51
+ padding-right: 0.25em;
52
+ }
53
+
54
+ .ark-audio__done {
55
+ display: grid;
56
+ grid-template-columns: 1fr 0.2fr;
57
+ border-radius: 2rem;
58
+ }
59
+
60
+ .ark-audio__done audio {
61
+ width: 220px;
62
+ height: 54px;
63
+ outline: none;
64
+ }
65
+
66
+ .ark-audio:hover {
67
+ border: 2px solid var(--primary);
68
+ }
69
+
70
+ .ark-audio ark-button {
71
+ text-align: center;
72
+ margin: 0;
73
+ padding: 0;
74
+ }
75
+
76
+ .ark-audio__idle {
77
+ display: grid;
78
+ grid-template-columns: repeat(2, 1fr);
79
+ width: 100%;
80
+ text-align: center;
81
+ align-items: center;
82
+ }
83
+ `
@@ -0,0 +1,2 @@
1
+ import styles from './ark.css.js'
2
+ export default styles
@@ -0,0 +1,64 @@
1
+ CAMERA
2
+ ======
3
+
4
+ The ``ark-camera`` allows the user to get access to the current device camera.
5
+
6
+
7
+ Examples
8
+ --------
9
+
10
+ **The camera needs buttons with events assigned that makes use of the internal methods of the component**
11
+
12
+ ``` html
13
+ <ark-camera></ark-camera>
14
+
15
+ <ark-button listen on-click="takepicture">Take photo</ark-button>
16
+ <ark-button listen on-click="startCamera">Start</ark-button>
17
+ <ark-button listen on-click="stopCamera">stop</ark-button>
18
+ ```
19
+
20
+ **Define functions to enable the events in the buttons**
21
+
22
+ ``` javascript
23
+ takepicture() {
24
+ this.photo.setAttribute('src', this.camera['dataURL'](200, 200))
25
+ }
26
+
27
+ startCamera() {
28
+ this.camera['start']()
29
+ }
30
+
31
+ stopCamera() {
32
+ this.camera['stop']()
33
+ }
34
+ ```
35
+
36
+
37
+ Attributes
38
+ ----------
39
+
40
+ | Name | Type | Default | options | Description |
41
+ | :---------: | :----: | :-----: | :--------------------------------------------: | :---------------------: |
42
+ | width | string | 320 | - | width of camera window |
43
+ | height | string | 320 | - | height of camera window |
44
+ | facing-mode | string | user | ``user``, ``environment``, ``left``, ``right`` | camera orientation |
45
+
46
+ Properties
47
+ ----------
48
+
49
+ | Name | Type | Default | options | Description |
50
+ | :--------: | :----: | :-----: | :--------------------------------------------: | :---------------------: |
51
+ | width | string | 320 | - | width of camera window |
52
+ | height | string | 320 | - | height of camera window |
53
+ | facingMode | string | user | ``user``, ``environment``, ``left``, ``right`` | camera orientation |
54
+
55
+
56
+ Methods
57
+ -------
58
+
59
+ | Name | Parameters | Description |
60
+ | :------------------: | :-------------------: | :--------------------------: |
61
+ | start | - | start device camera |
62
+ | stop | - | stop device camera |
63
+ | dataURL | ``width``, ``height`` | Saves a frame as png image |
64
+ | setCameraOrientation | ``facingMode`` | Set's the camera orientation |
@@ -0,0 +1,85 @@
1
+ import { Component } from '../../../base/component/index.js'
2
+ import styles from '../styles/index.js'
3
+
4
+ const tag = 'ark-camera'
5
+ export class Camera extends Component {
6
+ init (context = {}) {
7
+ this.width = this.width || context.width || 320
8
+ this.height = this.height || context.height || 320
9
+ this.facingMode = this.facingMode || context.facingMode || 'user'
10
+ this.global = context.global || window
11
+
12
+ return super.init()
13
+ }
14
+
15
+ reflectedProperties () {
16
+ return ['width', 'height', 'facingMode']
17
+ }
18
+
19
+ render () {
20
+ this.content = /* html */`
21
+ <canvas class="ark-camera__canvas"></canvas>
22
+ <video class="ark-camera__video" playsinline autoplay></video>
23
+ `
24
+
25
+ this.video.addEventListener('canplay', _ => {
26
+ this.video.setAttribute('width', `${this.width}px`)
27
+ this.video.setAttribute('height', `${this.height}px`)
28
+ this.canvas.setAttribute('width', `${this.width}px`)
29
+ this.canvas.setAttribute('height', `${this.height}px`)
30
+ }, false)
31
+
32
+ return super.render()
33
+ }
34
+
35
+ /** @returns {string} */
36
+ dataURL (width = null, height = null) {
37
+ /** @type {HTMLCanvasElement} */
38
+ const canvas = (this.canvas.cloneNode(true))
39
+
40
+ canvas.width = width || this.width
41
+ canvas.height = height || this.height
42
+ canvas.getContext('2d').drawImage(
43
+ this.video,
44
+ 0, 0, this.width, this.height,
45
+ 0, 0, width, height)
46
+
47
+ return canvas.toDataURL('image/jpg')
48
+ }
49
+
50
+ async start () {
51
+ const stream = await this.global.navigator.mediaDevices.getUserMedia({
52
+ video: {
53
+ width: this.width,
54
+ height: this.height,
55
+ facingMode: this.facingMode
56
+ },
57
+ audio: false
58
+ })
59
+
60
+ this.video.srcObject = stream
61
+ }
62
+
63
+ stop () {
64
+ // @ts-ignore
65
+ const tracks = this.video.srcObject ? this.video.srcObject.getTracks() : []
66
+ tracks.forEach(track => track.stop())
67
+ }
68
+
69
+ async setCameraOrientation (facingMode) {
70
+ this.stop()
71
+ this.facingMode = facingMode
72
+ await this.start()
73
+ }
74
+
75
+ /** @returns {HTMLVideoElement} */
76
+ get video () {
77
+ return this.querySelector('.ark-camera__video')
78
+ }
79
+
80
+ /** @returns {HTMLCanvasElement} */
81
+ get canvas () {
82
+ return this.querySelector('.ark-camera__canvas')
83
+ }
84
+ }
85
+ Component.define(tag, Camera, styles)
@@ -0,0 +1,104 @@
1
+ import './camera.js'
2
+
3
+ const mockGlobal = () => ({
4
+ navigator: {
5
+ mediaDevices: {
6
+ __stops: 0,
7
+ async getUserMedia(_options) {
8
+ return { getTracks: () => [{stop: () => {this.__stops += 1}}] }
9
+ }
10
+ }
11
+ }
12
+ })
13
+
14
+ describe('Camera', () => {
15
+ let container = null
16
+ beforeEach(() => {
17
+ container = document.createElement('div')
18
+ document.body.appendChild(container)
19
+ })
20
+
21
+ afterEach(() => {
22
+ container.remove()
23
+ container = null
24
+ })
25
+
26
+ it('can be instantiated', () => {
27
+ container.innerHTML = `
28
+ <ark-camera></ark-camera>
29
+ `
30
+ const camera = container.querySelector('ark-camera')
31
+ expect(camera).toBeTruthy()
32
+
33
+ expect(camera).toBe(camera.init())
34
+ })
35
+
36
+ it('sets its dimensions on canplay event', async () => {
37
+ container.innerHTML = `
38
+ <ark-camera width="50" height="80"></ark-camera>
39
+ `
40
+ const camera = container.querySelector('ark-camera')
41
+ const video = camera.select('video')
42
+ const canvas = camera.select('canvas')
43
+
44
+ video.dispatchEvent(new Event('canplay'))
45
+
46
+ expect(video.width).toEqual(50)
47
+ expect(video.height).toEqual(80)
48
+
49
+ expect(canvas.width).toEqual(50)
50
+ expect(canvas.height).toEqual(80)
51
+ })
52
+
53
+ it('can start video recording', async () => {
54
+ container.innerHTML = `
55
+ <ark-camera></ark-camera>
56
+ `
57
+ const camera = container.querySelector('ark-camera')
58
+ camera.init({global: mockGlobal()})
59
+
60
+ await camera.start()
61
+
62
+ expect(camera.video.srcObject.getTracks()).toBeTruthy()
63
+ })
64
+
65
+ it('can stop video recording', async () => {
66
+ container.innerHTML = `
67
+ <ark-camera></ark-camera>
68
+ `
69
+ const camera = container.querySelector('ark-camera')
70
+ const global = mockGlobal()
71
+ camera.init({global: global})
72
+
73
+ await camera.start()
74
+
75
+ camera.stop()
76
+
77
+ expect(global.navigator.mediaDevices.__stops).toEqual(1)
78
+ })
79
+
80
+ it('can set the camera orientation', async () => {
81
+ container.innerHTML = `
82
+ <ark-camera></ark-camera>
83
+ `
84
+ const camera = container.querySelector('ark-camera')
85
+ const global = mockGlobal()
86
+ camera.init({global: global})
87
+
88
+ await camera.setCameraOrientation('environment')
89
+
90
+ expect(camera.facingMode).toEqual('environment')
91
+ })
92
+
93
+ it('gets the dataURL (base64) of its containing canvas', async () => {
94
+ container.innerHTML = `
95
+ <ark-camera></ark-camera>
96
+ `
97
+ const camera = container.querySelector('ark-camera')
98
+ camera.init({global: mockGlobal()})
99
+
100
+ const data = camera.dataURL()
101
+
102
+ expect(data).toBeTruthy()
103
+ })
104
+ })
@@ -0,0 +1 @@
1
+ export { Camera } from './components/camera.js'