|
|
@@ -3,12 +3,11 @@
|
|
3
|
3
|
import Spinner from '@atlaskit/spinner';
|
|
4
|
4
|
import Bourne from '@hapi/bourne';
|
|
5
|
5
|
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
|
6
|
|
-import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
7
|
|
-import uuid from 'uuid';
|
|
|
6
|
+import React, { useState, useEffect, useCallback } from 'react';
|
|
8
|
7
|
|
|
9
|
8
|
import { Dialog, hideDialog, openDialog } from '../../base/dialog';
|
|
10
|
9
|
import { translate } from '../../base/i18n';
|
|
11
|
|
-import { Icon, IconCloseSmall, IconPlusCircle, IconShareDesktop } from '../../base/icons';
|
|
|
10
|
+import { Icon, IconCloseSmall, IconShareDesktop } from '../../base/icons';
|
|
12
|
11
|
import { browser, JitsiTrackErrors } from '../../base/lib-jitsi-meet';
|
|
13
|
12
|
import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
|
|
14
|
13
|
import { VIDEO_TYPE } from '../../base/media';
|
|
|
@@ -18,62 +17,20 @@ import { Tooltip } from '../../base/tooltip';
|
|
18
|
17
|
import { getLocalVideoTrack } from '../../base/tracks';
|
|
19
|
18
|
import { showErrorNotification } from '../../notifications';
|
|
20
|
19
|
import { toggleBackgroundEffect } from '../actions';
|
|
21
|
|
-import { VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
|
22
|
|
-import { resizeImage, toDataURL } from '../functions';
|
|
|
20
|
+import { IMAGES, BACKGROUNDS_LIMIT, VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
|
|
|
21
|
+import { toDataURL } from '../functions';
|
|
23
|
22
|
import logger from '../logger';
|
|
24
|
23
|
|
|
|
24
|
+import UploadImageButton from './UploadImageButton';
|
|
25
|
25
|
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
|
26
|
26
|
|
|
27
|
|
-
|
|
28
|
|
-type Image = {
|
|
29
|
|
- tooltip?: string,
|
|
30
|
|
- id: string,
|
|
31
|
|
- src: string
|
|
32
|
|
-}
|
|
33
|
|
-
|
|
34
|
|
-// The limit of virtual background uploads is 24. When the number
|
|
35
|
|
-// of uploads is 25 we trigger the deleteStoredImage function to delete
|
|
36
|
|
-// the first/oldest uploaded background.
|
|
37
|
|
-const backgroundsLimit = 25;
|
|
38
|
|
-const images: Array<Image> = [
|
|
39
|
|
- {
|
|
40
|
|
- tooltip: 'image1',
|
|
41
|
|
- id: '1',
|
|
42
|
|
- src: 'images/virtual-background/background-1.jpg'
|
|
43
|
|
- },
|
|
44
|
|
- {
|
|
45
|
|
- tooltip: 'image2',
|
|
46
|
|
- id: '2',
|
|
47
|
|
- src: 'images/virtual-background/background-2.jpg'
|
|
48
|
|
- },
|
|
49
|
|
- {
|
|
50
|
|
- tooltip: 'image3',
|
|
51
|
|
- id: '3',
|
|
52
|
|
- src: 'images/virtual-background/background-3.jpg'
|
|
53
|
|
- },
|
|
54
|
|
- {
|
|
55
|
|
- tooltip: 'image4',
|
|
56
|
|
- id: '4',
|
|
57
|
|
- src: 'images/virtual-background/background-4.jpg'
|
|
58
|
|
- },
|
|
59
|
|
- {
|
|
60
|
|
- tooltip: 'image5',
|
|
61
|
|
- id: '5',
|
|
62
|
|
- src: 'images/virtual-background/background-5.jpg'
|
|
63
|
|
- },
|
|
64
|
|
- {
|
|
65
|
|
- tooltip: 'image6',
|
|
66
|
|
- id: '6',
|
|
67
|
|
- src: 'images/virtual-background/background-6.jpg'
|
|
68
|
|
- },
|
|
69
|
|
- {
|
|
70
|
|
- tooltip: 'image7',
|
|
71
|
|
- id: '7',
|
|
72
|
|
- src: 'images/virtual-background/background-7.jpg'
|
|
73
|
|
- }
|
|
74
|
|
-];
|
|
75
|
27
|
type Props = {
|
|
76
|
28
|
|
|
|
29
|
+ /**
|
|
|
30
|
+ * The list of Images to choose from.
|
|
|
31
|
+ */
|
|
|
32
|
+ _images: Array<Image>,
|
|
|
33
|
+
|
|
77
|
34
|
/**
|
|
78
|
35
|
* The current local flip x status.
|
|
79
|
36
|
*/
|
|
|
@@ -89,6 +46,11 @@ type Props = {
|
|
89
|
46
|
*/
|
|
90
|
47
|
_selectedThumbnail: string,
|
|
91
|
48
|
|
|
|
49
|
+ /**
|
|
|
50
|
+ * If the upload button should be displayed or not.
|
|
|
51
|
+ */
|
|
|
52
|
+ _showUploadButton: boolean,
|
|
|
53
|
+
|
|
92
|
54
|
/**
|
|
93
|
55
|
* Returns the selected virtual background object.
|
|
94
|
56
|
*/
|
|
|
@@ -128,11 +90,15 @@ const onError = event => {
|
|
128
|
90
|
*/
|
|
129
|
91
|
function _mapStateToProps(state): Object {
|
|
130
|
92
|
const { localFlipX } = state['features/base/settings'];
|
|
|
93
|
+ const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
|
|
|
94
|
+ const hasBrandingImages = Boolean(dynamicBrandingImages.length);
|
|
131
|
95
|
|
|
132
|
96
|
return {
|
|
133
|
97
|
_localFlipX: Boolean(localFlipX),
|
|
|
98
|
+ _images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
|
|
134
|
99
|
_virtualBackground: state['features/virtual-background'],
|
|
135
|
100
|
_selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
|
|
|
101
|
+ _showUploadButton: !(hasBrandingImages || state['features/base/config'].disableAddingBackgroundImages),
|
|
136
|
102
|
_jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
|
|
137
|
103
|
};
|
|
138
|
104
|
}
|
|
|
@@ -145,9 +111,11 @@ const VirtualBackgroundDialog = translate(connect(_mapStateToProps)(VirtualBackg
|
|
145
|
111
|
* @returns {ReactElement}
|
|
146
|
112
|
*/
|
|
147
|
113
|
function VirtualBackground({
|
|
148
|
|
- _localFlipX,
|
|
|
114
|
+ _images,
|
|
149
|
115
|
_jitsiTrack,
|
|
|
116
|
+ _localFlipX,
|
|
150
|
117
|
_selectedThumbnail,
|
|
|
118
|
+ _showUploadButton,
|
|
151
|
119
|
_virtualBackground,
|
|
152
|
120
|
dispatch,
|
|
153
|
121
|
initialOptions,
|
|
|
@@ -158,7 +126,7 @@ function VirtualBackground({
|
|
158
|
126
|
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
|
159
|
127
|
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
|
160
|
128
|
const [ loading, setLoading ] = useState(false);
|
|
161
|
|
- const uploadImageButton: Object = useRef(null);
|
|
|
129
|
+
|
|
162
|
130
|
const [ activeDesktopVideo ] = useState(_virtualBackground?.virtualSource?.videoType === VIDEO_TYPE.DESKTOP
|
|
163
|
131
|
? _virtualBackground.virtualSource
|
|
164
|
132
|
: null);
|
|
|
@@ -186,7 +154,7 @@ function VirtualBackground({
|
|
186
|
154
|
// Preventing localStorage QUOTA_EXCEEDED_ERR
|
|
187
|
155
|
err && setStoredImages(storedImages.slice(1));
|
|
188
|
156
|
}
|
|
189
|
|
- if (storedImages.length === backgroundsLimit) {
|
|
|
157
|
+ if (storedImages.length === BACKGROUNDS_LIMIT) {
|
|
190
|
158
|
setStoredImages(storedImages.slice(1));
|
|
191
|
159
|
}
|
|
192
|
160
|
}, [ storedImages ]);
|
|
|
@@ -321,61 +289,27 @@ function VirtualBackground({
|
|
321
|
289
|
|
|
322
|
290
|
const setImageBackground = useCallback(async e => {
|
|
323
|
291
|
const imageId = e.currentTarget.getAttribute('data-imageid');
|
|
324
|
|
- const image = images.find(img => img.id === imageId);
|
|
|
292
|
+ const image = _images.find(img => img.id === imageId);
|
|
325
|
293
|
|
|
326
|
294
|
if (image) {
|
|
327
|
|
- const url = await toDataURL(image.src);
|
|
328
|
|
-
|
|
329
|
|
- setOptions({
|
|
330
|
|
- backgroundType: 'image',
|
|
331
|
|
- enabled: true,
|
|
332
|
|
- url,
|
|
333
|
|
- selectedThumbnail: image.id
|
|
334
|
|
- });
|
|
335
|
|
- logger.info('Image setted for virtual background preview!');
|
|
|
295
|
+ try {
|
|
|
296
|
+ const url = await toDataURL(image.src);
|
|
|
297
|
+
|
|
|
298
|
+ setOptions({
|
|
|
299
|
+ backgroundType: 'image',
|
|
|
300
|
+ enabled: true,
|
|
|
301
|
+ url,
|
|
|
302
|
+ selectedThumbnail: image.id
|
|
|
303
|
+ });
|
|
|
304
|
+ logger.info('Image set for virtual background preview!');
|
|
|
305
|
+ } catch (err) {
|
|
|
306
|
+ logger.error('Could not fetch virtual background image:', err);
|
|
|
307
|
+ }
|
|
336
|
308
|
|
|
337
|
309
|
setLoading(false);
|
|
338
|
310
|
}
|
|
339
|
311
|
}, []);
|
|
340
|
312
|
|
|
341
|
|
- const uploadImage = useCallback(async e => {
|
|
342
|
|
- const reader = new FileReader();
|
|
343
|
|
- const imageFile = e.target.files;
|
|
344
|
|
-
|
|
345
|
|
- reader.readAsDataURL(imageFile[0]);
|
|
346
|
|
- reader.onload = async () => {
|
|
347
|
|
- const url = await resizeImage(reader.result);
|
|
348
|
|
- const uuId = uuid.v4();
|
|
349
|
|
-
|
|
350
|
|
- setStoredImages([
|
|
351
|
|
- ...storedImages,
|
|
352
|
|
- {
|
|
353
|
|
- id: uuId,
|
|
354
|
|
- src: url
|
|
355
|
|
- }
|
|
356
|
|
- ]);
|
|
357
|
|
- setOptions({
|
|
358
|
|
- backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
|
359
|
|
- enabled: true,
|
|
360
|
|
- url,
|
|
361
|
|
- selectedThumbnail: uuId
|
|
362
|
|
- });
|
|
363
|
|
- };
|
|
364
|
|
- logger.info('New virtual background image uploaded!');
|
|
365
|
|
-
|
|
366
|
|
- reader.onerror = () => {
|
|
367
|
|
- setLoading(false);
|
|
368
|
|
- logger.error('Failed to upload virtual image!');
|
|
369
|
|
- };
|
|
370
|
|
- }, [ dispatch, storedImages ]);
|
|
371
|
|
-
|
|
372
|
|
- const uploadImageKeyPress = useCallback(e => {
|
|
373
|
|
- if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
|
|
374
|
|
- e.preventDefault();
|
|
375
|
|
- uploadImageButton.current.click();
|
|
376
|
|
- }
|
|
377
|
|
- }, [ uploadImageButton.current ]);
|
|
378
|
|
-
|
|
379
|
313
|
const setImageBackgroundKeyPress = useCallback(e => {
|
|
380
|
314
|
if (e.key === ' ' || e.key === 'Enter') {
|
|
381
|
315
|
e.preventDefault();
|
|
|
@@ -448,25 +382,13 @@ function VirtualBackground({
|
|
448
|
382
|
</div>
|
|
449
|
383
|
) : (
|
|
450
|
384
|
<div>
|
|
451
|
|
- {previewIsLoaded && <label
|
|
452
|
|
- aria-label = { t('virtualBackground.uploadImage') }
|
|
453
|
|
- className = 'file-upload-label'
|
|
454
|
|
- htmlFor = 'file-upload'
|
|
455
|
|
- onKeyPress = { uploadImageKeyPress }
|
|
456
|
|
- tabIndex = { 0 } >
|
|
457
|
|
- <Icon
|
|
458
|
|
- className = { 'add-background' }
|
|
459
|
|
- size = { 20 }
|
|
460
|
|
- src = { IconPlusCircle } />
|
|
461
|
|
- {t('virtualBackground.addBackground')}
|
|
462
|
|
- </label> }
|
|
463
|
|
- <input
|
|
464
|
|
- accept = 'image/*'
|
|
465
|
|
- className = 'file-upload-btn'
|
|
466
|
|
- id = 'file-upload'
|
|
467
|
|
- onChange = { uploadImage }
|
|
468
|
|
- ref = { uploadImageButton }
|
|
469
|
|
- type = 'file' />
|
|
|
385
|
+ {_showUploadButton
|
|
|
386
|
+ && <UploadImageButton
|
|
|
387
|
+ setLoading = { setLoading }
|
|
|
388
|
+ setOptions = { setOptions }
|
|
|
389
|
+ setStoredImages = { setStoredImages }
|
|
|
390
|
+ showLabel = { previewIsLoaded }
|
|
|
391
|
+ storedImages = { storedImages } />}
|
|
470
|
392
|
<div
|
|
471
|
393
|
className = 'virtual-background-dialog'
|
|
472
|
394
|
role = 'radiogroup'
|
|
|
@@ -535,7 +457,7 @@ function VirtualBackground({
|
|
535
|
457
|
src = { IconShareDesktop } />
|
|
536
|
458
|
</div>
|
|
537
|
459
|
</Tooltip>
|
|
538
|
|
- {images.map(image => (
|
|
|
460
|
+ {_images.map(image => (
|
|
539
|
461
|
<Tooltip
|
|
540
|
462
|
content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
|
|
541
|
463
|
key = { image.id }
|