@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.
- package/Makefile +49 -0
- package/README.md +47 -0
- package/knowarkjs.code-workspace +29 -0
- package/lib/base/component/README.rst +113 -0
- package/lib/base/component/component.js +115 -0
- package/lib/base/component/component.test.js +327 -0
- package/lib/base/component/index.js +3 -0
- package/lib/base/index.js +1 -0
- package/lib/base/styles/index.js +3 -0
- package/lib/base/styles/styles.js +320 -0
- package/lib/base/utils/define.js +21 -0
- package/lib/base/utils/define.test.js +62 -0
- package/lib/base/utils/format.js +24 -0
- package/lib/base/utils/format.test.js +19 -0
- package/lib/base/utils/helpers.js +96 -0
- package/lib/base/utils/helpers.test.js +154 -0
- package/lib/base/utils/index.js +5 -0
- package/lib/base/utils/slots.js +18 -0
- package/lib/base/utils/slots.test.js +52 -0
- package/lib/base/utils/uuid.js +9 -0
- package/lib/base/utils/uuid.test.js +19 -0
- package/lib/components/audio/README.md +22 -0
- package/lib/components/audio/components/audio.js +103 -0
- package/lib/components/audio/components/audio.test.js +127 -0
- package/lib/components/audio/index.js +1 -0
- package/lib/components/audio/styles/ark.css.js +83 -0
- package/lib/components/audio/styles/index.js +2 -0
- package/lib/components/camera/README.md +64 -0
- package/lib/components/camera/components/camera.js +85 -0
- package/lib/components/camera/components/camera.test.js +104 -0
- package/lib/components/camera/index.js +1 -0
- package/lib/components/camera/styles/ark.css.js +17 -0
- package/lib/components/camera/styles/index.js +2 -0
- package/lib/components/capture/components/capture.js +54 -0
- package/lib/components/capture/components/capture.test.js +112 -0
- package/lib/components/capture/index.js +1 -0
- package/lib/components/droparea/README.md +51 -0
- package/lib/components/droparea/components/droparea-preview.js +159 -0
- package/lib/components/droparea/components/droparea-preview.test.js +105 -0
- package/lib/components/droparea/components/droparea.js +165 -0
- package/lib/components/droparea/components/droparea.test.js +320 -0
- package/lib/components/droparea/index.js +1 -0
- package/lib/components/droparea/styles/ark.css.js +235 -0
- package/lib/components/droparea/styles/index.js +3 -0
- package/lib/components/emit/components/emit.js +33 -0
- package/lib/components/emit/components/emit.test.js +138 -0
- package/lib/components/emit/index.js +1 -0
- package/lib/components/index.js +9 -0
- package/lib/components/list/README.md +103 -0
- package/lib/components/list/components/item.test.js +93 -0
- package/lib/components/list/components/list.item.js +22 -0
- package/lib/components/list/components/list.js +96 -0
- package/lib/components/list/components/list.test.js +267 -0
- package/lib/components/list/index.js +2 -0
- package/lib/components/paginator/README.md +32 -0
- package/lib/components/paginator/components/paginator.js +110 -0
- package/lib/components/paginator/components/paginator.test.js +131 -0
- package/lib/components/paginator/index.js +1 -0
- package/lib/components/paginator/styles/ark.css.js +196 -0
- package/lib/components/paginator/styles/index.js +2 -0
- package/lib/components/spinner/README.md +41 -0
- package/lib/components/spinner/components/spinner.js +105 -0
- package/lib/components/spinner/components/spinner.test.js +50 -0
- package/lib/components/spinner/index.js +1 -0
- package/lib/components/spinner/styles/ark.css.js +658 -0
- package/lib/components/spinner/styles/index.js +2 -0
- package/lib/components/splitview/README.md +63 -0
- package/lib/components/splitview/components/splitview.detail.js +46 -0
- package/lib/components/splitview/components/splitview.detail.test.js +92 -0
- package/lib/components/splitview/components/splitview.js +69 -0
- package/lib/components/splitview/components/splitview.master.js +26 -0
- package/lib/components/splitview/components/splitview.master.test.js +55 -0
- package/lib/components/splitview/components/splitview.test.js +76 -0
- package/lib/components/splitview/index.js +3 -0
- package/lib/components/translate/README.md +56 -0
- package/lib/components/translate/components/translate.js +100 -0
- package/lib/components/translate/components/translate.test.js +226 -0
- package/lib/components/translate/index.js +1 -0
- package/lib/index.js +2 -0
- package/package.json +68 -0
- package/showcase/design/.htaccess +8 -0
- package/showcase/design/core/factories/development/development.factory.js +5 -0
- package/showcase/design/core/factories/development/index.js +1 -0
- package/showcase/design/core/factories/index.js +11 -0
- package/showcase/design/core/factories/standard.factory.js +19 -0
- package/showcase/design/index.html +22 -0
- package/showcase/design/index.js +7 -0
- package/showcase/design/screens/base/audio/audioDemo.js +32 -0
- package/showcase/design/screens/base/audio/index.js +25 -0
- package/showcase/design/screens/base/camera/cameraDemo.js +83 -0
- package/showcase/design/screens/base/camera/index.js +25 -0
- package/showcase/design/screens/base/droparea/dropareaDemo.js +88 -0
- package/showcase/design/screens/base/droparea/index.js +25 -0
- package/showcase/design/screens/base/index.js +42 -0
- package/showcase/design/screens/base/list/index.js +25 -0
- package/showcase/design/screens/base/list/listDemo.js +89 -0
- package/showcase/design/screens/base/paginator/index.js +25 -0
- package/showcase/design/screens/base/paginator/paginatorDemo.js +82 -0
- package/showcase/design/screens/base/root.component.js +294 -0
- package/showcase/design/screens/base/root.routes.js +28 -0
- package/showcase/design/screens/base/spinner/index.js +25 -0
- package/showcase/design/screens/base/spinner/spinnerDemo.js +55 -0
- package/showcase/design/screens/base/splitview/detailDemo.js +40 -0
- package/showcase/design/screens/base/splitview/index.js +25 -0
- package/showcase/design/screens/base/splitview/splitViewDemo.js +58 -0
- package/showcase/design/screens/base/translate/index.js +20 -0
- package/showcase/design/screens/base/translate/translateDemo.js +43 -0
- package/showcase/design/screens/main.js +12 -0
- package/showcase/design/screens/screens.routes.js +23 -0
- package/showcase/index.html +86 -0
- package/showcase/index.js +5 -0
- package/showcase/locales/en/default.json +5 -0
- package/showcase/locales/es/default.json +5 -0
- package/showcase/locales/fr/default.json +5 -0
- package/tsconfig.json +23 -0
- 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,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,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'
|