|
@@ -1,36 +1,37 @@
|
1
|
1
|
// @flow
|
2
|
2
|
/* eslint-disable react/jsx-no-bind, no-return-assign */
|
3
|
|
-import React, { useState } from 'react';
|
|
3
|
+import Spinner from '@atlaskit/spinner';
|
|
4
|
+import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
|
5
|
+import React, { useState, useEffect } from 'react';
|
|
6
|
+import uuid from 'uuid';
|
4
|
7
|
|
5
|
8
|
import { Dialog } from '../../base/dialog';
|
6
|
9
|
import { translate } from '../../base/i18n';
|
7
|
|
-import { Icon, IconBlurBackground } from '../../base/icons';
|
|
10
|
+import { Icon, IconBlurBackground, IconCancelSelection } from '../../base/icons';
|
8
|
11
|
import { connect } from '../../base/redux';
|
9
|
12
|
import { Tooltip } from '../../base/tooltip';
|
10
|
13
|
import { toggleBackgroundEffect, setVirtualBackground } from '../actions';
|
|
14
|
+import { resizeImage, toDataURL } from '../functions';
|
|
15
|
+import logger from '../logger';
|
11
|
16
|
|
|
17
|
+// The limit of virtual background uploads is 21. When the number
|
|
18
|
+// of uploads is 22 we trigger the deleteStoredImage function to delete
|
|
19
|
+// the first/oldest uploaded background.
|
|
20
|
+const backgroundsLimit = 22;
|
12
|
21
|
const images = [
|
13
|
22
|
{
|
14
|
|
- tooltip: 'Image 1',
|
15
|
|
- name: 'background-1.jpg',
|
16
|
23
|
id: 1,
|
17
|
24
|
src: 'images/virtual-background/background-1.jpg'
|
18
|
25
|
},
|
19
|
26
|
{
|
20
|
|
- tooltip: 'Image 2',
|
21
|
|
- name: 'background-2.jpg',
|
22
|
27
|
id: 2,
|
23
|
28
|
src: 'images/virtual-background/background-2.jpg'
|
24
|
29
|
},
|
25
|
30
|
{
|
26
|
|
- tooltip: 'Image 3',
|
27
|
|
- name: 'background-3.jpg',
|
28
|
31
|
id: 3,
|
29
|
32
|
src: 'images/virtual-background/background-3.jpg'
|
30
|
33
|
},
|
31
|
34
|
{
|
32
|
|
- tooltip: 'Image 4',
|
33
|
|
- name: 'background-4.jpg',
|
34
|
35
|
id: 4,
|
35
|
36
|
src: 'images/virtual-background/background-4.jpg'
|
36
|
37
|
}
|
|
@@ -54,23 +55,81 @@ type Props = {
|
54
|
55
|
* @returns {ReactElement}
|
55
|
56
|
*/
|
56
|
57
|
function VirtualBackground({ dispatch, t }: Props) {
|
|
58
|
+ const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
|
59
|
+ const [ storedImages, setStoredImages ] = useState((localImages && JSON.parse(localImages)) || []);
|
|
60
|
+ const [ loading, isloading ] = useState(false);
|
|
61
|
+
|
|
62
|
+ const deleteStoredImage = image => {
|
|
63
|
+ setStoredImages(storedImages.filter(item => item !== image));
|
|
64
|
+ };
|
|
65
|
+
|
|
66
|
+ /**
|
|
67
|
+ * Updates stored images on local storage.
|
|
68
|
+ */
|
|
69
|
+ useEffect(() => {
|
|
70
|
+ jitsiLocalStorage.setItem('virtualBackgrounds', JSON.stringify(storedImages));
|
|
71
|
+ if (storedImages.length === backgroundsLimit) {
|
|
72
|
+ deleteStoredImage(storedImages[0]);
|
|
73
|
+ }
|
|
74
|
+ }, [ storedImages ]);
|
|
75
|
+
|
57
|
76
|
const [ selected, setSelected ] = useState('');
|
58
|
|
- const enableBlur = () => {
|
|
77
|
+ const enableBlur = async () => {
|
|
78
|
+ isloading(true);
|
59
|
79
|
setSelected('blur');
|
60
|
|
- dispatch(setVirtualBackground('', false));
|
61
|
|
- dispatch(toggleBackgroundEffect(true));
|
|
80
|
+ await dispatch(setVirtualBackground('', false));
|
|
81
|
+ await dispatch(toggleBackgroundEffect(true));
|
|
82
|
+ isloading(false);
|
62
|
83
|
};
|
63
|
84
|
|
64
|
|
- const removeBackground = () => {
|
|
85
|
+ const removeBackground = async () => {
|
|
86
|
+ isloading(true);
|
65
|
87
|
setSelected('none');
|
66
|
|
- dispatch(setVirtualBackground('', false));
|
67
|
|
- dispatch(toggleBackgroundEffect(false));
|
|
88
|
+ await dispatch(setVirtualBackground('', false));
|
|
89
|
+ await dispatch(toggleBackgroundEffect(false));
|
|
90
|
+ isloading(false);
|
|
91
|
+ };
|
|
92
|
+
|
|
93
|
+ const setUploadedImageBackground = async image => {
|
|
94
|
+ isloading(true);
|
|
95
|
+ setSelected(image.id);
|
|
96
|
+ await dispatch(setVirtualBackground(image.src, true));
|
|
97
|
+ await dispatch(toggleBackgroundEffect(true));
|
|
98
|
+ isloading(false);
|
68
|
99
|
};
|
69
|
100
|
|
70
|
|
- const addImageBackground = image => {
|
|
101
|
+ const setImageBackground = async image => {
|
|
102
|
+ isloading(true);
|
71
|
103
|
setSelected(image.id);
|
72
|
|
- dispatch(setVirtualBackground(image.src, true));
|
73
|
|
- dispatch(toggleBackgroundEffect(true));
|
|
104
|
+ await dispatch(setVirtualBackground(await toDataURL(image.src), true));
|
|
105
|
+ await dispatch(toggleBackgroundEffect(true));
|
|
106
|
+ isloading(false);
|
|
107
|
+ };
|
|
108
|
+
|
|
109
|
+ const uploadImage = async imageFile => {
|
|
110
|
+ const reader = new FileReader();
|
|
111
|
+
|
|
112
|
+ reader.readAsDataURL(imageFile[0]);
|
|
113
|
+ reader.onload = async () => {
|
|
114
|
+ const resizedImage = await resizeImage(reader.result);
|
|
115
|
+
|
|
116
|
+ isloading(true);
|
|
117
|
+ setStoredImages([
|
|
118
|
+ ...storedImages,
|
|
119
|
+ {
|
|
120
|
+ id: uuid.v4(),
|
|
121
|
+ src: resizedImage
|
|
122
|
+ }
|
|
123
|
+ ]);
|
|
124
|
+
|
|
125
|
+ await dispatch(setVirtualBackground(resizedImage, true));
|
|
126
|
+ await dispatch(toggleBackgroundEffect(true));
|
|
127
|
+ isloading(false);
|
|
128
|
+ };
|
|
129
|
+ reader.onerror = () => {
|
|
130
|
+ isloading(false);
|
|
131
|
+ logger.error('Failed to upload virtual image!');
|
|
132
|
+ };
|
74
|
133
|
};
|
75
|
134
|
|
76
|
135
|
return (
|
|
@@ -79,38 +138,79 @@ function VirtualBackground({ dispatch, t }: Props) {
|
79
|
138
|
submitDisabled = { false }
|
80
|
139
|
titleKey = { 'virtualBackground.title' }
|
81
|
140
|
width = 'small'>
|
82
|
|
- <div className = 'virtual-background-dialog'>
|
83
|
|
- <Tooltip
|
84
|
|
- content = { t('virtualBackground.removeBackground') }
|
85
|
|
- position = { 'top' }>
|
86
|
|
- <div
|
87
|
|
- className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
|
88
|
|
- onClick = { () => removeBackground() }>
|
89
|
|
- None
|
|
141
|
+ {loading ? (
|
|
142
|
+ <div>
|
|
143
|
+ <span className = 'loading-content-text'>{t('virtualBackground.pleaseWait')}</span>
|
|
144
|
+ <Spinner
|
|
145
|
+ isCompleting = { false }
|
|
146
|
+ size = 'medium' />
|
|
147
|
+ </div>
|
|
148
|
+ ) : (
|
|
149
|
+ <div>
|
|
150
|
+ <div className = 'virtual-background-dialog'>
|
|
151
|
+ <Tooltip
|
|
152
|
+ content = { t('virtualBackground.removeBackground') }
|
|
153
|
+ position = { 'top' }>
|
|
154
|
+ <div
|
|
155
|
+ className = { selected === 'none' ? 'none-selected' : 'virtual-background-none' }
|
|
156
|
+ onClick = { removeBackground }>
|
|
157
|
+ {t('virtualBackground.none')}
|
|
158
|
+ </div>
|
|
159
|
+ </Tooltip>
|
|
160
|
+ <Tooltip
|
|
161
|
+ content = { t('virtualBackground.enableBlur') }
|
|
162
|
+ position = { 'top' }>
|
|
163
|
+ <Icon
|
|
164
|
+ className = { selected === 'blur' ? 'blur-selected' : '' }
|
|
165
|
+ onClick = { () => enableBlur() }
|
|
166
|
+ size = { 50 }
|
|
167
|
+ src = { IconBlurBackground } />
|
|
168
|
+ </Tooltip>
|
|
169
|
+ {images.map((image, index) => (
|
|
170
|
+ <img
|
|
171
|
+ className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
|
172
|
+ key = { index }
|
|
173
|
+ onClick = { () => setImageBackground(image) }
|
|
174
|
+ onError = { event => event.target.style.display = 'none' }
|
|
175
|
+ src = { image.src } />
|
|
176
|
+ ))}
|
|
177
|
+ <Tooltip
|
|
178
|
+ content = { t('virtualBackground.uploadImage') }
|
|
179
|
+ position = { 'top' }>
|
|
180
|
+ <label
|
|
181
|
+ className = 'custom-file-upload'
|
|
182
|
+ htmlFor = 'file-upload'>
|
|
183
|
+ +
|
|
184
|
+ </label>
|
|
185
|
+ <input
|
|
186
|
+ accept = 'image/*'
|
|
187
|
+ className = 'file-upload-btn'
|
|
188
|
+ id = 'file-upload'
|
|
189
|
+ onChange = { e => uploadImage(e.target.files) }
|
|
190
|
+ type = 'file' />
|
|
191
|
+ </Tooltip>
|
|
192
|
+ </div>
|
|
193
|
+
|
|
194
|
+ <div className = 'virtual-background-dialog'>
|
|
195
|
+ {storedImages.map((image, index) => (
|
|
196
|
+ <div
|
|
197
|
+ className = { 'thumbnail-container' }
|
|
198
|
+ key = { index }>
|
|
199
|
+ <img
|
|
200
|
+ className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
|
201
|
+ onClick = { () => setUploadedImageBackground(image) }
|
|
202
|
+ onError = { event => event.target.style.display = 'none' }
|
|
203
|
+ src = { image.src } />
|
|
204
|
+ <Icon
|
|
205
|
+ className = { 'delete-image-icon' }
|
|
206
|
+ onClick = { () => deleteStoredImage(image) }
|
|
207
|
+ size = { 15 }
|
|
208
|
+ src = { IconCancelSelection } />
|
|
209
|
+ </div>
|
|
210
|
+ ))}
|
90
|
211
|
</div>
|
91
|
|
- </Tooltip>
|
92
|
|
- <Tooltip
|
93
|
|
- content = { t('virtualBackground.enableBlur') }
|
94
|
|
- position = { 'top' }>
|
95
|
|
- <Icon
|
96
|
|
- className = { selected === 'blur' ? 'blur-selected' : '' }
|
97
|
|
- onClick = { () => enableBlur() }
|
98
|
|
- size = { 50 }
|
99
|
|
- src = { IconBlurBackground } />
|
100
|
|
- </Tooltip>
|
101
|
|
- {images.map((image, index) => (
|
102
|
|
- <Tooltip
|
103
|
|
- content = { image.tooltip }
|
104
|
|
- key = { index }
|
105
|
|
- position = { 'top' }>
|
106
|
|
- <img
|
107
|
|
- className = { selected === image.id ? 'thumbnail-selected' : 'thumbnail' }
|
108
|
|
- onClick = { () => addImageBackground(image) }
|
109
|
|
- onError = { event => event.target.style.display = 'none' }
|
110
|
|
- src = { image.src } />
|
111
|
|
- </Tooltip>
|
112
|
|
- ))}
|
113
|
|
- </div>
|
|
212
|
+ </div>
|
|
213
|
+ )}
|
114
|
214
|
</Dialog>
|
115
|
215
|
);
|
116
|
216
|
}
|