瀏覽代碼

feat(desktop-picker): Add spinner and disable button if sources aren't initialized.

j8
hristoterezov 7 年之前
父節點
當前提交
c7b0028652

+ 7
- 0
css/modals/desktop-picker/_desktop-picker.scss 查看文件

26
             width: 30%;
26
             width: 30%;
27
         }
27
         }
28
     }
28
     }
29
+
30
+    &-spinner {
31
+        justify-content: center;
32
+        display: flex;
33
+        height: 100%;
34
+        align-items: center;
35
+    }
29
 }
36
 }
30
 
37
 
31
 .desktop-picker-source {
38
 .desktop-picker-source {

+ 0
- 18
react/features/desktop-picker/actionTypes.js 查看文件

1
-/**
2
- * Action to remove known DesktopCapturerSources.
3
- *
4
- * {
5
- *     type: RESET_DESKTOP_SOURCES,
6
- * }
7
- */
8
-export const RESET_DESKTOP_SOURCES = Symbol('RESET_DESKTOP_SOURCES');
9
-
10
-/**
11
- * Action to replace stored DesktopCapturerSources with new sources.
12
- *
13
- * {
14
- *     type: UPDATE_DESKTOP_SOURCES,
15
- *     sources: {Array}
16
- * }
17
- */
18
-export const UPDATE_DESKTOP_SOURCES = Symbol('UPDATE_DESKTOP_SOURCES');

+ 4
- 75
react/features/desktop-picker/actions.js 查看文件

1
 import { openDialog } from '../base/dialog';
1
 import { openDialog } from '../base/dialog';
2
 
2
 
3
-import {
4
-    RESET_DESKTOP_SOURCES,
5
-    UPDATE_DESKTOP_SOURCES
6
-} from './actionTypes';
7
 import { DesktopPicker } from './components';
3
 import { DesktopPicker } from './components';
8
 
4
 
9
-const logger = require('jitsi-meet-logger').getLogger(__filename);
10
-
11
-/**
12
- * Begins a request to get available DesktopCapturerSources.
13
- *
14
- * @param {Array} types - An array with DesktopCapturerSource type strings.
15
- * @param {Object} options - Additional configuration for getting a list of
16
- * sources.
17
- * @param {Object} options.thumbnailSize - The desired height and width of the
18
- * return native image object used for the preview image of the source.
19
- * @returns {Function}
20
- */
21
-export function obtainDesktopSources(types, options = {}) {
22
-    const capturerOptions = {
23
-        types
24
-    };
25
-
26
-    if (options.thumbnailSize) {
27
-        capturerOptions.thumbnailSize = options.thumbnailSize;
28
-    }
29
-
30
-    return dispatch => {
31
-        const { JitsiMeetElectron } = window;
32
-
33
-        if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) {
34
-            JitsiMeetElectron.obtainDesktopStreams(
35
-                sources => dispatch(updateDesktopSources(sources)),
36
-                error =>
37
-                    logger.error(
38
-                        `Error while obtaining desktop sources: ${error}`),
39
-                capturerOptions
40
-            );
41
-        } else {
42
-            logger.error(
43
-                'Called JitsiMeetElectron.obtainDesktopStreams'
44
-                    + ' but it is not defined');
45
-        }
46
-    };
47
-}
48
-
49
-/**
50
- * Signals to remove all stored DesktopCapturerSources.
51
- *
52
- * @returns {{
53
- *     type: RESET_DESKTOP_SOURCES
54
- * }}
55
- */
56
-export function resetDesktopSources() {
57
-    return {
58
-        type: RESET_DESKTOP_SOURCES
59
-    };
60
-}
61
-
62
 /**
5
 /**
63
  * Signals to open a dialog with the DesktopPicker component.
6
  * Signals to open a dialog with the DesktopPicker component.
64
  *
7
  *
67
  * a DesktopCapturerSource has been chosen.
10
  * a DesktopCapturerSource has been chosen.
68
  * @returns {Object}
11
  * @returns {Object}
69
  */
12
  */
70
-export function showDesktopPicker(options, onSourceChoose) {
13
+export function showDesktopPicker(options = {}, onSourceChoose) {
14
+    const { desktopSharingSources } = options;
15
+
71
     return openDialog(DesktopPicker, {
16
     return openDialog(DesktopPicker, {
72
-        options,
17
+        desktopSharingSources,
73
         onSourceChoose
18
         onSourceChoose
74
     });
19
     });
75
 }
20
 }
76
-
77
-/**
78
- * Signals new DesktopCapturerSources have been received.
79
- *
80
- * @param {Object} sources - Arrays with DesktopCapturerSources.
81
- * @returns {{
82
- *     type: UPDATE_DESKTOP_SOURCES,
83
- *     sources: Array
84
- * }}
85
- */
86
-export function updateDesktopSources(sources) {
87
-    return {
88
-        type: UPDATE_DESKTOP_SOURCES,
89
-        sources
90
-    };
91
-}

+ 81
- 96
react/features/desktop-picker/components/DesktopPicker.js 查看文件

8
 import { Dialog, hideDialog } from '../../base/dialog';
8
 import { Dialog, hideDialog } from '../../base/dialog';
9
 import { translate } from '../../base/i18n';
9
 import { translate } from '../../base/i18n';
10
 
10
 
11
-import { obtainDesktopSources, resetDesktopSources } from '../actions';
12
 import DesktopPickerPane from './DesktopPickerPane';
11
 import DesktopPickerPane from './DesktopPickerPane';
12
+import { obtainDesktopSources } from '../functions';
13
 
13
 
14
 const THUMBNAIL_SIZE = {
14
 const THUMBNAIL_SIZE = {
15
     height: 300,
15
     height: 300,
16
     width: 300
16
     width: 300
17
 };
17
 };
18
-const UPDATE_INTERVAL = 1000;
18
+
19
+const UPDATE_INTERVAL = 2000;
19
 
20
 
20
 type TabConfiguration = {
21
 type TabConfiguration = {
21
     defaultSelected?: boolean,
22
     defaultSelected?: boolean,
22
-    label: string,
23
-    type: string
23
+    label: string
24
 };
24
 };
25
-const TAB_CONFIGURATIONS: Array<TabConfiguration> = [
26
-    {
25
+
26
+const TAB_CONFIGURATIONS: { [type: string]: TabConfiguration} = {
27
+    screen: {
27
         /**
28
         /**
28
          * The indicator which determines whether this tab configuration is
29
          * The indicator which determines whether this tab configuration is
29
          * selected by default.
30
          * selected by default.
31
          * @type {boolean}
32
          * @type {boolean}
32
          */
33
          */
33
         defaultSelected: true,
34
         defaultSelected: true,
34
-        label: 'dialog.yourEntireScreen',
35
-        type: 'screen'
35
+        label: 'dialog.yourEntireScreen'
36
     },
36
     },
37
-    {
38
-        label: 'dialog.applicationWindow',
39
-        type: 'window'
37
+    window: {
38
+        label: 'dialog.applicationWindow'
40
     }
39
     }
41
-];
42
-const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type);
40
+};
41
+
42
+const VALID_TYPES = Object.keys(TAB_CONFIGURATIONS);
43
 
43
 
44
 /**
44
 /**
45
  * React component for DesktopPicker.
45
  * React component for DesktopPicker.
47
  * @extends Component
47
  * @extends Component
48
  */
48
  */
49
 class DesktopPicker extends Component {
49
 class DesktopPicker extends Component {
50
-    /**
51
-     * Default values for DesktopPicker component's properties.
52
-     *
53
-     * @static
54
-     */
55
-    static defaultProps = {
56
-        options: {}
57
-    };
58
-
59
     /**
50
     /**
60
      * DesktopPicker component's property types.
51
      * DesktopPicker component's property types.
61
      *
52
      *
62
      * @static
53
      * @static
63
      */
54
      */
64
     static propTypes = {
55
     static propTypes = {
56
+
57
+        /**
58
+         * An array with desktop sharing sources to be displayed.
59
+         */
60
+        desktopSharingSources: PropTypes.arrayOf(PropTypes.string),
61
+
65
         /**
62
         /**
66
          * Used to request DesktopCapturerSources.
63
          * Used to request DesktopCapturerSources.
67
          */
64
          */
73
          */
70
          */
74
         onSourceChoose: PropTypes.func,
71
         onSourceChoose: PropTypes.func,
75
 
72
 
76
-        /**
77
-         * An object with options related to desktop sharing.
78
-         */
79
-        options: PropTypes.object,
80
-
81
-        /**
82
-         * An object with arrays of DesktopCapturerSources. The key should be
83
-         * the source type.
84
-         */
85
-        sources: PropTypes.object,
86
-
87
         /**
73
         /**
88
          * Used to obtain translations.
74
          * Used to obtain translations.
89
          */
75
          */
94
 
80
 
95
     state = {
81
     state = {
96
         selectedSource: {},
82
         selectedSource: {},
97
-        tabsToPopulate: [],
98
-        typesToFetch: []
83
+        sources: {},
84
+        types: []
99
     };
85
     };
100
 
86
 
101
     /**
87
     /**
112
         this._onPreviewClick = this._onPreviewClick.bind(this);
98
         this._onPreviewClick = this._onPreviewClick.bind(this);
113
         this._onSubmit = this._onSubmit.bind(this);
99
         this._onSubmit = this._onSubmit.bind(this);
114
         this._updateSources = this._updateSources.bind(this);
100
         this._updateSources = this._updateSources.bind(this);
101
+
102
+        this.state.types
103
+            = this._getValidTypes(this.props.desktopSharingSources);
115
     }
104
     }
116
 
105
 
117
     /**
106
     /**
118
-     * Perform an immediate update request for DesktopCapturerSources and begin
119
-     * requesting updates at an interval.
107
+     * Starts polling.
120
      *
108
      *
121
      * @inheritdoc
109
      * @inheritdoc
110
+     * @returns {void}
122
      */
111
      */
123
-    componentWillMount() {
124
-        const { desktopSharingSources } = this.props.options;
125
-
126
-        this._onSourceTypesConfigChanged(
127
-            desktopSharingSources);
128
-        this._updateSources();
112
+    componentDidMount() {
129
         this._startPolling();
113
         this._startPolling();
130
     }
114
     }
131
 
115
 
139
      * @returns {void}
123
      * @returns {void}
140
      */
124
      */
141
     componentWillReceiveProps(nextProps) {
125
     componentWillReceiveProps(nextProps) {
142
-        if (!this.state.selectedSource.id
143
-                && nextProps.sources.screen.length) {
126
+        const { desktopSharingSources } = nextProps;
127
+
128
+        /**
129
+         * Do only reference check in order to not calculate the types on every
130
+         * update. This is enough for our use case and we don't need value
131
+         * checking because if the value is the same we won't change the
132
+         * reference for the desktopSharingSources array.
133
+         */
134
+        if (desktopSharingSources !== this.props.desktopSharingSources) {
144
             this.setState({
135
             this.setState({
145
-                selectedSource: {
146
-                    id: nextProps.sources.screen[0].id,
147
-                    type: 'screen'
148
-                }
136
+                types: this._getValidTypes(desktopSharingSources)
149
             });
137
             });
150
         }
138
         }
151
-
152
-        const { desktopSharingSources } = this.props.options;
153
-
154
-        this._onSourceTypesConfigChanged(
155
-            desktopSharingSources);
156
     }
139
     }
157
 
140
 
158
     /**
141
     /**
162
      */
145
      */
163
     componentWillUnmount() {
146
     componentWillUnmount() {
164
         this._stopPolling();
147
         this._stopPolling();
165
-        this.props.dispatch(resetDesktopSources());
166
     }
148
     }
167
 
149
 
168
     /**
150
     /**
174
         return (
156
         return (
175
             <Dialog
157
             <Dialog
176
                 isModal = { false }
158
                 isModal = { false }
159
+                okDisabled = { Boolean(!this.state.selectedSource.id) }
177
                 okTitleKey = 'dialog.Share'
160
                 okTitleKey = 'dialog.Share'
178
                 onCancel = { this._onCloseModal }
161
                 onCancel = { this._onCloseModal }
179
                 onSubmit = { this._onSubmit }
162
                 onSubmit = { this._onSubmit }
220
     }
203
     }
221
 
204
 
222
     /**
205
     /**
223
-     * Handles changing of allowed desktop sharing source types.
206
+     * Extracts only the valid types from the passed {@code types}.
224
      *
207
      *
225
-     * @param {Array<string>} desktopSharingSourceTypes - The types that will be
226
-     * fetched and displayed.
227
-     * @returns {void}
208
+     * @param {Array<string>} types - The types to filter.
209
+     * @returns {Array<string>} The filtered types.
228
      */
210
      */
229
-    _onSourceTypesConfigChanged(desktopSharingSourceTypes = []) {
230
-        const tabsToPopulate
231
-            = TAB_CONFIGURATIONS.filter(({ type }) =>
232
-                desktopSharingSourceTypes.includes(type)
233
-                    && VALID_TYPES.includes(type));
234
-
235
-        this.setState({
236
-            tabsToPopulate,
237
-            typesToFetch: tabsToPopulate.map(c => c.type)
238
-        });
211
+    _getValidTypes(types = []) {
212
+        return types.filter(
213
+            type => VALID_TYPES.includes(type));
239
     }
214
     }
240
 
215
 
241
     _onSubmit: () => void;
216
     _onSubmit: () => void;
259
      * @returns {ReactElement}
234
      * @returns {ReactElement}
260
      */
235
      */
261
     _renderTabs() {
236
     _renderTabs() {
262
-        const { selectedSource } = this.state;
263
-        const { sources, t } = this.props;
237
+        const { selectedSource, sources, types } = this.state;
238
+        const { t } = this.props;
264
         const tabs
239
         const tabs
265
-            = this.state.tabsToPopulate.map(
266
-                ({ defaultSelected, label, type }) => {
240
+            = types.map(
241
+                type => {
242
+                    const { defaultSelected, label } = TAB_CONFIGURATIONS[type];
243
+
267
                     return {
244
                     return {
268
                         content: <DesktopPickerPane
245
                         content: <DesktopPickerPane
269
                             key = { type }
246
                             key = { type }
270
                             onClick = { this._onPreviewClick }
247
                             onClick = { this._onPreviewClick }
271
                             onDoubleClick = { this._onCloseModal }
248
                             onDoubleClick = { this._onCloseModal }
272
                             selectedSourceId = { selectedSource.id }
249
                             selectedSourceId = { selectedSource.id }
273
-                            sources = { sources[type] || [] }
250
+                            sources = { sources[type] }
274
                             type = { type } />,
251
                             type = { type } />,
275
                         defaultSelected,
252
                         defaultSelected,
276
                         label: t(label)
253
                         label: t(label)
288
      */
265
      */
289
     _startPolling() {
266
     _startPolling() {
290
         this._stopPolling();
267
         this._stopPolling();
268
+        this._updateSources();
291
         this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL);
269
         this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL);
292
     }
270
     }
293
 
271
 
311
      * @returns {void}
289
      * @returns {void}
312
      */
290
      */
313
     _updateSources() {
291
     _updateSources() {
314
-        this.props.dispatch(obtainDesktopSources(
315
-            this.state.typesToFetch,
316
-            {
317
-                THUMBNAIL_SIZE
318
-            }
319
-        ));
320
-    }
321
-}
292
+        const { types } = this.state;
293
+
294
+        if (types.length > 0) {
295
+            obtainDesktopSources(
296
+                this.state.types,
297
+                { thumbnailSize: THUMBNAIL_SIZE }
298
+            )
299
+            .then(sources => {
300
+                const nextState: Object = {
301
+                    sources
302
+                };
303
+
304
+                // FIXME: selectedSource when screen is disabled, when the
305
+                // source has been removed or when the selectedTab is changed!!!
306
+                if (!this.state.selectedSource.id
307
+                        && sources.screen.length > 0) {
308
+                    nextState.selectedSource = {
309
+                        id: sources.screen[0].id,
310
+                        type: 'screen'
311
+                    };
312
+                }
322
 
313
 
323
-/**
324
- * Maps (parts of) the Redux state to the associated DesktopPicker's props.
325
- *
326
- * @param {Object} state - Redux state.
327
- * @private
328
- * @returns {{
329
- *     sources: Object
330
- * }}
331
- */
332
-function _mapStateToProps(state) {
333
-    return {
334
-        sources: state['features/desktop-picker']
335
-    };
314
+                // TODO: Maybe check if we have stopped the timer and unmounted
315
+                // the component.
316
+                this.setState(nextState);
317
+            })
318
+            .catch(() => { /* ignore */ });
319
+        }
320
+    }
336
 }
321
 }
337
 
322
 
338
-export default translate(connect(_mapStateToProps)(DesktopPicker));
323
+export default translate(connect()(DesktopPicker));

+ 10
- 2
react/features/desktop-picker/components/DesktopPickerPane.js 查看文件

1
+import Spinner from '@atlaskit/spinner';
1
 import PropTypes from 'prop-types';
2
 import PropTypes from 'prop-types';
2
 import React, { Component } from 'react';
3
 import React, { Component } from 'react';
3
 
4
 
60
         const classNames
61
         const classNames
61
             = `desktop-picker-pane default-scrollbar source-type-${type}`;
62
             = `desktop-picker-pane default-scrollbar source-type-${type}`;
62
         const previews
63
         const previews
63
-            = sources.map(
64
+            = sources ? sources.map(
64
                 source =>
65
                 source =>
65
 
66
 
66
                     // eslint-disable-next-line react/jsx-wrap-multilines
67
                     // eslint-disable-next-line react/jsx-wrap-multilines
70
                         onDoubleClick = { onDoubleClick }
71
                         onDoubleClick = { onDoubleClick }
71
                         selected = { source.id === selectedSourceId }
72
                         selected = { source.id === selectedSourceId }
72
                         source = { source }
73
                         source = { source }
73
-                        type = { type } />);
74
+                        type = { type } />)
75
+                : ( // eslint-disable-line no-extra-parens
76
+                    <div className = 'desktop-picker-pane-spinner'>
77
+                        <Spinner
78
+                            isCompleting = { false }
79
+                            size = 'medium' />
80
+                    </div>
81
+                );
74
 
82
 
75
         return (
83
         return (
76
             <div className = { classNames }>
84
             <div className = { classNames }>

+ 73
- 0
react/features/desktop-picker/functions.js 查看文件

1
+
2
+const logger = require('jitsi-meet-logger').getLogger(__filename);
3
+
4
+/**
5
+ * Begins a request to get available DesktopCapturerSources.
6
+ *
7
+ * @param {Array} types - An array with DesktopCapturerSource type strings.
8
+ * @param {Object} options - Additional configuration for getting a list of
9
+ * sources.
10
+ * @param {Object} options.thumbnailSize - The desired height and width of the
11
+ * return native image object used for the preview image of the source.
12
+ * @returns {Function}
13
+ */
14
+export function obtainDesktopSources(types, options = {}) {
15
+    const capturerOptions = {
16
+        types
17
+    };
18
+
19
+    if (options.thumbnailSize) {
20
+        capturerOptions.thumbnailSize = options.thumbnailSize;
21
+    }
22
+
23
+    return new Promise((resolve, reject) => {
24
+        const { JitsiMeetElectron } = window;
25
+
26
+        if (JitsiMeetElectron && JitsiMeetElectron.obtainDesktopStreams) {
27
+            JitsiMeetElectron.obtainDesktopStreams(
28
+                sources => resolve(_seperateSourcesByType(sources)),
29
+                error => {
30
+                    logger.error(
31
+                        `Error while obtaining desktop sources: ${error}`);
32
+                    reject(error);
33
+                },
34
+                capturerOptions
35
+            );
36
+        } else {
37
+            const reason = 'Called JitsiMeetElectron.obtainDesktopStreams'
38
+                + ' but it is not defined';
39
+
40
+            logger.error(reason);
41
+
42
+            return Promise.reject(new Error(reason));
43
+        }
44
+    });
45
+}
46
+
47
+
48
+/**
49
+ * Converts an array of DesktopCapturerSources to an object with types for keys
50
+ * and values being an array with sources of the key's type.
51
+ *
52
+ * @param {Array} sources - DesktopCapturerSources.
53
+ * @private
54
+ * @returns {Object} An object with the sources split into seperate arrays based
55
+ * on source type.
56
+ */
57
+function _seperateSourcesByType(sources = []) {
58
+    const sourcesByType = {
59
+        screen: [],
60
+        window: []
61
+    };
62
+
63
+    sources.forEach(source => {
64
+        const idParts = source.id.split(':');
65
+        const type = idParts[0];
66
+
67
+        if (sourcesByType[type]) {
68
+            sourcesByType[type].push(source);
69
+        }
70
+    });
71
+
72
+    return sourcesByType;
73
+}

+ 0
- 3
react/features/desktop-picker/index.js 查看文件

1
 export * from './actions';
1
 export * from './actions';
2
-export * from './actionTypes';
3
 export * from './components';
2
 export * from './components';
4
-
5
-import './reducer';

+ 0
- 62
react/features/desktop-picker/reducer.js 查看文件

1
-import { ReducerRegistry } from '../base/redux';
2
-
3
-import {
4
-    RESET_DESKTOP_SOURCES,
5
-    UPDATE_DESKTOP_SOURCES
6
-} from './actionTypes';
7
-
8
-const DEFAULT_STATE = {
9
-    screen: [],
10
-    window: []
11
-};
12
-
13
-/**
14
- * Listen for actions that mutate the known available DesktopCapturerSources.
15
- *
16
- * @param {Object[]} state - Current state.
17
- * @param {Object} action - Action object.
18
- * @param {string} action.type - Type of action.
19
- * @param {Array} action.sources - DesktopCapturerSources.
20
- * @returns {Object}
21
- */
22
-ReducerRegistry.register(
23
-    'features/desktop-picker',
24
-    (state = DEFAULT_STATE, action) => {
25
-        switch (action.type) {
26
-        case RESET_DESKTOP_SOURCES:
27
-            return { ...DEFAULT_STATE };
28
-
29
-        case UPDATE_DESKTOP_SOURCES:
30
-            return _seperateSourcesByType(action.sources);
31
-
32
-        default:
33
-            return state;
34
-        }
35
-    });
36
-
37
-/**
38
- * Converts an array of DesktopCapturerSources to an object with types for keys
39
- * and values being an array with sources of the key's type.
40
- *
41
- * @param {Array} sources - DesktopCapturerSources.
42
- * @private
43
- * @returns {Object} An object with the sources split into seperate arrays based
44
- * on source type.
45
- */
46
-function _seperateSourcesByType(sources = []) {
47
-    const sourcesByType = {
48
-        screen: [],
49
-        window: []
50
-    };
51
-
52
-    sources.forEach(source => {
53
-        const idParts = source.id.split(':');
54
-        const type = idParts[0];
55
-
56
-        if (sourcesByType[type]) {
57
-            sourcesByType[type].push(source);
58
-        }
59
-    });
60
-
61
-    return sourcesByType;
62
-}

Loading…
取消
儲存