소스 검색

feat(dynamic-branding): Add branding option for virtual backgrounds

master
Vlad Piersec 4 년 전
부모
커밋
f9cc813e91

+ 4
- 0
config.js 파일 보기

909
     */
909
     */
910
     // dynamicBrandingUrl: '',
910
     // dynamicBrandingUrl: '',
911
 
911
 
912
+    // When true the user cannot add more images to be used as virtual background.
913
+    // Only the default ones from will be available.
914
+    // disableAddingBackgroundImages: false,
915
+
912
     // Sets the background transparency level. '0' is fully transparent, '1' is opaque.
916
     // Sets the background transparency level. '0' is fully transparent, '1' is opaque.
913
     // backgroundAlpha: 1,
917
     // backgroundAlpha: 1,
914
 
918
 

+ 1
- 0
react/features/app/middlewares.web.js 파일 보기

2
 
2
 
3
 import '../authentication/middleware';
3
 import '../authentication/middleware';
4
 import '../base/devices/middleware';
4
 import '../base/devices/middleware';
5
+import '../dynamic-branding/middleware';
5
 import '../e2ee/middleware';
6
 import '../e2ee/middleware';
6
 import '../external-api/middleware';
7
 import '../external-api/middleware';
7
 import '../keyboard-shortcuts/middleware';
8
 import '../keyboard-shortcuts/middleware';

+ 1
- 0
react/features/base/config/configWhitelist.js 파일 보기

84
     'disableAEC',
84
     'disableAEC',
85
     'disableAGC',
85
     'disableAGC',
86
     'disableAP',
86
     'disableAP',
87
+    'disableAddingBackgroundImages',
87
     'disableAudioLevels',
88
     'disableAudioLevels',
88
     'disableChatSmileys',
89
     'disableChatSmileys',
89
     'disableDeepLinking',
90
     'disableDeepLinking',

+ 3
- 3
react/features/dynamic-branding/functions.js 파일 보기

7
  * @param {string} path - The URL path.
7
  * @param {string} path - The URL path.
8
  * @returns {string}
8
  * @returns {string}
9
  */
9
  */
10
-export function extractFqnFromPath(path: string) {
11
-    const parts = path.split('/');
10
+export function extractFqnFromPath() {
11
+    const parts = window.location.pathname.split('/');
12
     const len = parts.length;
12
     const len = parts.length;
13
 
13
 
14
     return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
14
     return parts.length > 2 ? `${parts[len - 2]}/${parts[len - 1]}` : '';
28
     }
28
     }
29
 
29
 
30
     const baseUrl = state['features/base/config'].brandingDataUrl;
30
     const baseUrl = state['features/base/config'].brandingDataUrl;
31
-    const fqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
31
+    const fqn = extractFqnFromPath();
32
 
32
 
33
     if (baseUrl && fqn) {
33
     if (baseUrl && fqn) {
34
         return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;
34
         return `${baseUrl}?conferenceFqn=${encodeURIComponent(fqn)}`;

+ 18
- 0
react/features/dynamic-branding/middleware.js 파일 보기

1
+// @flow
2
+
3
+import { APP_WILL_MOUNT } from '../base/app';
4
+import { MiddlewareRegistry } from '../base/redux';
5
+
6
+import { fetchCustomBrandingData } from './actions';
7
+
8
+MiddlewareRegistry.register(store => next => action => {
9
+    switch (action.type) {
10
+    case APP_WILL_MOUNT: {
11
+
12
+        store.dispatch(fetchCustomBrandingData());
13
+        break;
14
+    }
15
+    }
16
+
17
+    return next(action);
18
+});

+ 41
- 3
react/features/dynamic-branding/reducer.js 파일 보기

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { ReducerRegistry } from '../base/redux';
3
 import { ReducerRegistry } from '../base/redux';
4
+import { type Image } from '../virtual-background/constants';
4
 
5
 
5
 import {
6
 import {
6
     SET_DYNAMIC_BRANDING_DATA,
7
     SET_DYNAMIC_BRANDING_DATA,
113
      * @public
114
      * @public
114
      * @type {boolean}
115
      * @type {boolean}
115
      */
116
      */
116
-    useDynamicBrandingData: false
117
+    useDynamicBrandingData: false,
118
+
119
+    /**
120
+     * An array of images to be used as virtual backgrounds instead of the default ones.
121
+     *
122
+     * @public
123
+     * @type {Array<Object>}
124
+     */
125
+    virtualBackgrounds: []
117
 };
126
 };
118
 
127
 
119
 /**
128
 /**
131
             inviteDomain,
140
             inviteDomain,
132
             logoClickUrl,
141
             logoClickUrl,
133
             logoImageUrl,
142
             logoImageUrl,
134
-            premeetingBackground
143
+            premeetingBackground,
144
+            virtualBackgrounds
135
         } = action.value;
145
         } = action.value;
136
 
146
 
137
         return {
147
         return {
146
             premeetingBackground,
156
             premeetingBackground,
147
             customizationFailed: false,
157
             customizationFailed: false,
148
             customizationReady: true,
158
             customizationReady: true,
149
-            useDynamicBrandingData: true
159
+            useDynamicBrandingData: true,
160
+            virtualBackgrounds: formatImages(virtualBackgrounds || [])
150
         };
161
         };
151
     }
162
     }
152
     case SET_DYNAMIC_BRANDING_FAILED: {
163
     case SET_DYNAMIC_BRANDING_FAILED: {
166
 
177
 
167
     return state;
178
     return state;
168
 });
179
 });
180
+
181
+/**
182
+ * Transforms the branding images into an array of Images objects ready
183
+ * to be used as virtual backgrounds.
184
+ *
185
+ * @param {Array<string>} images -
186
+ * @private
187
+ * @returns {{Props}}
188
+ */
189
+function formatImages(images: Array<string> | Array<Object>): Array<Image> {
190
+    return images.map((img, i) => {
191
+        let src;
192
+        let tooltip;
193
+
194
+        if (typeof img === 'object') {
195
+            ({ src, tooltip } = img);
196
+        } else {
197
+            src = img;
198
+        }
199
+
200
+        return {
201
+            id: `branding-${i}`,
202
+            src,
203
+            tooltip
204
+        };
205
+    });
206
+}

+ 1
- 1
react/features/feedback/actions.js 파일 보기

131
             return Promise.resolve();
131
             return Promise.resolve();
132
         }
132
         }
133
 
133
 
134
-        const meetingFqn = extractFqnFromPath(state['features/base/connection'].locationURL.pathname);
134
+        const meetingFqn = extractFqnFromPath();
135
         const feedbackData = {
135
         const feedbackData = {
136
             ...feedback,
136
             ...feedback,
137
             sessionId: conference.sessionId,
137
             sessionId: conference.sessionId,

+ 1
- 19
react/features/large-video/components/LargeVideo.web.js 파일 보기

5
 import { Watermarks } from '../../base/react';
5
 import { Watermarks } from '../../base/react';
6
 import { connect } from '../../base/redux';
6
 import { connect } from '../../base/redux';
7
 import { setColorAlpha } from '../../base/util';
7
 import { setColorAlpha } from '../../base/util';
8
-import { fetchCustomBrandingData } from '../../dynamic-branding';
9
 import { SharedVideo } from '../../shared-video/components/web';
8
 import { SharedVideo } from '../../shared-video/components/web';
10
 import { Captions } from '../../subtitles/';
9
 import { Captions } from '../../subtitles/';
11
 
10
 
28
      */
27
      */
29
      _customBackgroundImageUrl: string,
28
      _customBackgroundImageUrl: string,
30
 
29
 
31
-    /**
32
-     * Fetches the branding data.
33
-     */
34
-    _fetchCustomBrandingData: Function,
35
-
36
     /**
30
     /**
37
      * Prop that indicates whether the chat is open.
31
      * Prop that indicates whether the chat is open.
38
      */
32
      */
52
  * @extends Component
46
  * @extends Component
53
  */
47
  */
54
 class LargeVideo extends Component<Props> {
48
 class LargeVideo extends Component<Props> {
55
-    /**
56
-     * Implements React's {@link Component#componentDidMount}.
57
-     *
58
-     * @inheritdoc
59
-     */
60
-    componentDidMount() {
61
-        this.props._fetchCustomBrandingData();
62
-    }
63
 
49
 
64
     /**
50
     /**
65
      * Implements React's {@link Component#render()}.
51
      * Implements React's {@link Component#render()}.
167
     };
153
     };
168
 }
154
 }
169
 
155
 
170
-const _mapDispatchToProps = {
171
-    _fetchCustomBrandingData: fetchCustomBrandingData
172
-};
173
-
174
-export default connect(_mapStateToProps, _mapDispatchToProps)(LargeVideo);
156
+export default connect(_mapStateToProps)(LargeVideo);

+ 1
- 2
react/features/reactions/functions.any.js 파일 보기

55
     const { webhookProxyUrl: url } = state['features/base/config'];
55
     const { webhookProxyUrl: url } = state['features/base/config'];
56
     const { conference } = state['features/base/conference'];
56
     const { conference } = state['features/base/conference'];
57
     const { jwt } = state['features/base/jwt'];
57
     const { jwt } = state['features/base/jwt'];
58
-    const { locationURL } = state['features/base/connection'];
59
     const localParticipant = getLocalParticipant(state);
58
     const localParticipant = getLocalParticipant(state);
60
 
59
 
61
     const headers = {
60
     const headers = {
65
 
64
 
66
 
65
 
67
     const reqBody = {
66
     const reqBody = {
68
-        meetingFqn: extractFqnFromPath(locationURL.pathname),
67
+        meetingFqn: extractFqnFromPath(),
69
         sessionId: conference.sessionId,
68
         sessionId: conference.sessionId,
70
         submitted: Date.now(),
69
         submitted: Date.now(),
71
         reactions,
70
         reactions,

+ 125
- 0
react/features/virtual-background/components/UploadImageButton.js 파일 보기

1
+// @flow
2
+
3
+import React, { useCallback, useRef } from 'react';
4
+import uuid from 'uuid';
5
+
6
+import { translate } from '../../base/i18n';
7
+import { Icon, IconPlusCircle } from '../../base/icons';
8
+import { VIRTUAL_BACKGROUND_TYPE, type Image } from '../constants';
9
+import { resizeImage } from '../functions';
10
+import logger from '../logger';
11
+
12
+type Props = {
13
+
14
+    /**
15
+     * Callback used to set the 'loading' state of the parent component.
16
+     */
17
+    setLoading: Function,
18
+
19
+    /**
20
+     * Callback used to set the options.
21
+     */
22
+    setOptions: Function,
23
+
24
+    /**
25
+     * Callback used to set the storedImages array.
26
+     */
27
+    setStoredImages: Function,
28
+
29
+    /**
30
+     * A list of images locally stored.
31
+     */
32
+    storedImages: Array<Image>,
33
+
34
+    /**
35
+     * If a label should be displayed alongside the button.
36
+     */
37
+    showLabel: boolean,
38
+
39
+    /**
40
+     * Used for translation.
41
+     */
42
+    t: Function
43
+}
44
+
45
+/**
46
+ * Component used to upload an image.
47
+ *
48
+ * @param {Object} Props - The props of the component.
49
+ * @returns {React$Node}
50
+ */
51
+function UploadImageButton({
52
+    setLoading,
53
+    setOptions,
54
+    setStoredImages,
55
+    showLabel,
56
+    storedImages,
57
+    t
58
+}: Props) {
59
+    const uploadImageButton: Object = useRef(null);
60
+    const uploadImageKeyPress = useCallback(e => {
61
+        if (uploadImageButton.current && (e.key === ' ' || e.key === 'Enter')) {
62
+            e.preventDefault();
63
+            uploadImageButton.current.click();
64
+        }
65
+    }, [ uploadImageButton.current ]);
66
+
67
+
68
+    const uploadImage = useCallback(async e => {
69
+        const reader = new FileReader();
70
+        const imageFile = e.target.files;
71
+
72
+        reader.readAsDataURL(imageFile[0]);
73
+        reader.onload = async () => {
74
+            const url = await resizeImage(reader.result);
75
+            const uuId = uuid.v4();
76
+
77
+            setStoredImages([
78
+                ...storedImages,
79
+                {
80
+                    id: uuId,
81
+                    src: url
82
+                }
83
+            ]);
84
+            setOptions({
85
+                backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
86
+                enabled: true,
87
+                url,
88
+                selectedThumbnail: uuId
89
+            });
90
+        };
91
+        logger.info('New virtual background image uploaded!');
92
+
93
+        reader.onerror = () => {
94
+            setLoading(false);
95
+            logger.error('Failed to upload virtual image!');
96
+        };
97
+    }, [ storedImages ]);
98
+
99
+    return (
100
+        <>
101
+            {showLabel && <label
102
+                aria-label = { t('virtualBackground.uploadImage') }
103
+                className = 'file-upload-label'
104
+                htmlFor = 'file-upload'
105
+                onKeyPress = { uploadImageKeyPress }
106
+                tabIndex = { 0 } >
107
+                <Icon
108
+                    className = { 'add-background' }
109
+                    size = { 20 }
110
+                    src = { IconPlusCircle } />
111
+                {t('virtualBackground.addBackground')}
112
+            </label>}
113
+
114
+            <input
115
+                accept = 'image/*'
116
+                className = 'file-upload-btn'
117
+                id = 'file-upload'
118
+                onChange = { uploadImage }
119
+                ref = { uploadImageButton }
120
+                type = 'file' />
121
+        </>
122
+    );
123
+}
124
+
125
+export default translate(UploadImageButton);

+ 46
- 124
react/features/virtual-background/components/VirtualBackgroundDialog.js 파일 보기

3
 import Spinner from '@atlaskit/spinner';
3
 import Spinner from '@atlaskit/spinner';
4
 import Bourne from '@hapi/bourne';
4
 import Bourne from '@hapi/bourne';
5
 import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
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
 import { Dialog, hideDialog, openDialog } from '../../base/dialog';
8
 import { Dialog, hideDialog, openDialog } from '../../base/dialog';
10
 import { translate } from '../../base/i18n';
9
 import { translate } from '../../base/i18n';
11
-import { Icon, IconCloseSmall, IconPlusCircle, IconShareDesktop } from '../../base/icons';
10
+import { Icon, IconCloseSmall, IconShareDesktop } from '../../base/icons';
12
 import { browser, JitsiTrackErrors } from '../../base/lib-jitsi-meet';
11
 import { browser, JitsiTrackErrors } from '../../base/lib-jitsi-meet';
13
 import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
12
 import { createLocalTrack } from '../../base/lib-jitsi-meet/functions';
14
 import { VIDEO_TYPE } from '../../base/media';
13
 import { VIDEO_TYPE } from '../../base/media';
18
 import { getLocalVideoTrack } from '../../base/tracks';
17
 import { getLocalVideoTrack } from '../../base/tracks';
19
 import { showErrorNotification } from '../../notifications';
18
 import { showErrorNotification } from '../../notifications';
20
 import { toggleBackgroundEffect } from '../actions';
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
 import logger from '../logger';
22
 import logger from '../logger';
24
 
23
 
24
+import UploadImageButton from './UploadImageButton';
25
 import VirtualBackgroundPreview from './VirtualBackgroundPreview';
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
 type Props = {
27
 type Props = {
76
 
28
 
29
+    /**
30
+     * The list of Images to choose from.
31
+     */
32
+    _images: Array<Image>,
33
+
77
     /**
34
     /**
78
      * The current local flip x status.
35
      * The current local flip x status.
79
      */
36
      */
89
      */
46
      */
90
     _selectedThumbnail: string,
47
     _selectedThumbnail: string,
91
 
48
 
49
+    /**
50
+     * If the upload button should be displayed or not.
51
+     */
52
+    _showUploadButton: boolean,
53
+
92
     /**
54
     /**
93
      * Returns the selected virtual background object.
55
      * Returns the selected virtual background object.
94
      */
56
      */
128
  */
90
  */
129
 function _mapStateToProps(state): Object {
91
 function _mapStateToProps(state): Object {
130
     const { localFlipX } = state['features/base/settings'];
92
     const { localFlipX } = state['features/base/settings'];
93
+    const dynamicBrandingImages = state['features/dynamic-branding'].virtualBackgrounds;
94
+    const hasBrandingImages = Boolean(dynamicBrandingImages.length);
131
 
95
 
132
     return {
96
     return {
133
         _localFlipX: Boolean(localFlipX),
97
         _localFlipX: Boolean(localFlipX),
98
+        _images: (hasBrandingImages && dynamicBrandingImages) || IMAGES,
134
         _virtualBackground: state['features/virtual-background'],
99
         _virtualBackground: state['features/virtual-background'],
135
         _selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
100
         _selectedThumbnail: state['features/virtual-background'].selectedThumbnail,
101
+        _showUploadButton: !(hasBrandingImages || state['features/base/config'].disableAddingBackgroundImages),
136
         _jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
102
         _jitsiTrack: getLocalVideoTrack(state['features/base/tracks'])?.jitsiTrack
137
     };
103
     };
138
 }
104
 }
145
  * @returns {ReactElement}
111
  * @returns {ReactElement}
146
  */
112
  */
147
 function VirtualBackground({
113
 function VirtualBackground({
148
-    _localFlipX,
114
+    _images,
149
     _jitsiTrack,
115
     _jitsiTrack,
116
+    _localFlipX,
150
     _selectedThumbnail,
117
     _selectedThumbnail,
118
+    _showUploadButton,
151
     _virtualBackground,
119
     _virtualBackground,
152
     dispatch,
120
     dispatch,
153
     initialOptions,
121
     initialOptions,
158
     const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
126
     const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
159
     const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
127
     const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
160
     const [ loading, setLoading ] = useState(false);
128
     const [ loading, setLoading ] = useState(false);
161
-    const uploadImageButton: Object = useRef(null);
129
+
162
     const [ activeDesktopVideo ] = useState(_virtualBackground?.virtualSource?.videoType === VIDEO_TYPE.DESKTOP
130
     const [ activeDesktopVideo ] = useState(_virtualBackground?.virtualSource?.videoType === VIDEO_TYPE.DESKTOP
163
         ? _virtualBackground.virtualSource
131
         ? _virtualBackground.virtualSource
164
         : null);
132
         : null);
186
             // Preventing localStorage QUOTA_EXCEEDED_ERR
154
             // Preventing localStorage QUOTA_EXCEEDED_ERR
187
             err && setStoredImages(storedImages.slice(1));
155
             err && setStoredImages(storedImages.slice(1));
188
         }
156
         }
189
-        if (storedImages.length === backgroundsLimit) {
157
+        if (storedImages.length === BACKGROUNDS_LIMIT) {
190
             setStoredImages(storedImages.slice(1));
158
             setStoredImages(storedImages.slice(1));
191
         }
159
         }
192
     }, [ storedImages ]);
160
     }, [ storedImages ]);
321
 
289
 
322
     const setImageBackground = useCallback(async e => {
290
     const setImageBackground = useCallback(async e => {
323
         const imageId = e.currentTarget.getAttribute('data-imageid');
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
         if (image) {
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
             setLoading(false);
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
     const setImageBackgroundKeyPress = useCallback(e => {
313
     const setImageBackgroundKeyPress = useCallback(e => {
380
         if (e.key === ' ' || e.key === 'Enter') {
314
         if (e.key === ' ' || e.key === 'Enter') {
381
             e.preventDefault();
315
             e.preventDefault();
448
                 </div>
382
                 </div>
449
             ) : (
383
             ) : (
450
                 <div>
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
                     <div
392
                     <div
471
                         className = 'virtual-background-dialog'
393
                         className = 'virtual-background-dialog'
472
                         role = 'radiogroup'
394
                         role = 'radiogroup'
535
                                     src = { IconShareDesktop } />
457
                                     src = { IconShareDesktop } />
536
                             </div>
458
                             </div>
537
                         </Tooltip>
459
                         </Tooltip>
538
-                        {images.map(image => (
460
+                        {_images.map(image => (
539
                             <Tooltip
461
                             <Tooltip
540
                                 content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
462
                                 content = { image.tooltip && t(`virtualBackground.${image.tooltip}`) }
541
                                 key = { image.id }
463
                                 key = { image.id }

+ 53
- 0
react/features/virtual-background/constants.js 파일 보기

1
+// @flow
2
+
1
 /**
3
 /**
2
  * An enumeration of the different virtual background types.
4
  * An enumeration of the different virtual background types.
3
  *
5
  *
9
     BLUR: 'blur',
11
     BLUR: 'blur',
10
     NONE: 'none'
12
     NONE: 'none'
11
 };
13
 };
14
+
15
+
16
+export type Image = {
17
+    tooltip?: string,
18
+    id: string,
19
+    src: string
20
+}
21
+
22
+// The limit of virtual background uploads is 24. When the number
23
+// of uploads is 25 we trigger the deleteStoredImage function to delete
24
+// the first/oldest uploaded background.
25
+export const BACKGROUNDS_LIMIT = 25;
26
+
27
+
28
+export const IMAGES: Array<Image> = [
29
+    {
30
+        tooltip: 'image1',
31
+        id: '1',
32
+        src: 'images/virtual-background/background-1.jpg'
33
+    },
34
+    {
35
+        tooltip: 'image2',
36
+        id: '2',
37
+        src: 'images/virtual-background/background-2.jpg'
38
+    },
39
+    {
40
+        tooltip: 'image3',
41
+        id: '3',
42
+        src: 'images/virtual-background/background-3.jpg'
43
+    },
44
+    {
45
+        tooltip: 'image4',
46
+        id: '4',
47
+        src: 'images/virtual-background/background-4.jpg'
48
+    },
49
+    {
50
+        tooltip: 'image5',
51
+        id: '5',
52
+        src: 'images/virtual-background/background-5.jpg'
53
+    },
54
+    {
55
+        tooltip: 'image6',
56
+        id: '6',
57
+        src: 'images/virtual-background/background-6.jpg'
58
+    },
59
+    {
60
+        tooltip: 'image7',
61
+        id: '7',
62
+        src: 'images/virtual-background/background-7.jpg'
63
+    }
64
+];

Loading…
취소
저장