Преглед изворни кода

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,6 +26,13 @@
26 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 38
 .desktop-picker-source {

+ 0
- 18
react/features/desktop-picker/actionTypes.js Прегледај датотеку

@@ -1,18 +0,0 @@
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,64 +1,7 @@
1 1
 import { openDialog } from '../base/dialog';
2 2
 
3
-import {
4
-    RESET_DESKTOP_SOURCES,
5
-    UPDATE_DESKTOP_SOURCES
6
-} from './actionTypes';
7 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 6
  * Signals to open a dialog with the DesktopPicker component.
64 7
  *
@@ -67,25 +10,11 @@ export function resetDesktopSources() {
67 10
  * a DesktopCapturerSource has been chosen.
68 11
  * @returns {Object}
69 12
  */
70
-export function showDesktopPicker(options, onSourceChoose) {
13
+export function showDesktopPicker(options = {}, onSourceChoose) {
14
+    const { desktopSharingSources } = options;
15
+
71 16
     return openDialog(DesktopPicker, {
72
-        options,
17
+        desktopSharingSources,
73 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,22 +8,23 @@ import { connect } from 'react-redux';
8 8
 import { Dialog, hideDialog } from '../../base/dialog';
9 9
 import { translate } from '../../base/i18n';
10 10
 
11
-import { obtainDesktopSources, resetDesktopSources } from '../actions';
12 11
 import DesktopPickerPane from './DesktopPickerPane';
12
+import { obtainDesktopSources } from '../functions';
13 13
 
14 14
 const THUMBNAIL_SIZE = {
15 15
     height: 300,
16 16
     width: 300
17 17
 };
18
-const UPDATE_INTERVAL = 1000;
18
+
19
+const UPDATE_INTERVAL = 2000;
19 20
 
20 21
 type TabConfiguration = {
21 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 29
          * The indicator which determines whether this tab configuration is
29 30
          * selected by default.
@@ -31,15 +32,14 @@ const TAB_CONFIGURATIONS: Array<TabConfiguration> = [
31 32
          * @type {boolean}
32 33
          */
33 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 45
  * React component for DesktopPicker.
@@ -47,21 +47,18 @@ const VALID_TYPES = TAB_CONFIGURATIONS.map(c => c.type);
47 47
  * @extends Component
48 48
  */
49 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 51
      * DesktopPicker component's property types.
61 52
      *
62 53
      * @static
63 54
      */
64 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 63
          * Used to request DesktopCapturerSources.
67 64
          */
@@ -73,17 +70,6 @@ class DesktopPicker extends Component {
73 70
          */
74 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 74
          * Used to obtain translations.
89 75
          */
@@ -94,8 +80,8 @@ class DesktopPicker extends Component {
94 80
 
95 81
     state = {
96 82
         selectedSource: {},
97
-        tabsToPopulate: [],
98
-        typesToFetch: []
83
+        sources: {},
84
+        types: []
99 85
     };
100 86
 
101 87
     /**
@@ -112,20 +98,18 @@ class DesktopPicker extends Component {
112 98
         this._onPreviewClick = this._onPreviewClick.bind(this);
113 99
         this._onSubmit = this._onSubmit.bind(this);
114 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 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 113
         this._startPolling();
130 114
     }
131 115
 
@@ -139,20 +123,19 @@ class DesktopPicker extends Component {
139 123
      * @returns {void}
140 124
      */
141 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 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,7 +145,6 @@ class DesktopPicker extends Component {
162 145
      */
163 146
     componentWillUnmount() {
164 147
         this._stopPolling();
165
-        this.props.dispatch(resetDesktopSources());
166 148
     }
167 149
 
168 150
     /**
@@ -174,6 +156,7 @@ class DesktopPicker extends Component {
174 156
         return (
175 157
             <Dialog
176 158
                 isModal = { false }
159
+                okDisabled = { Boolean(!this.state.selectedSource.id) }
177 160
                 okTitleKey = 'dialog.Share'
178 161
                 onCancel = { this._onCloseModal }
179 162
                 onSubmit = { this._onSubmit }
@@ -220,22 +203,14 @@ class DesktopPicker extends Component {
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 216
     _onSubmit: () => void;
@@ -259,18 +234,20 @@ class DesktopPicker extends Component {
259 234
      * @returns {ReactElement}
260 235
      */
261 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 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 244
                     return {
268 245
                         content: <DesktopPickerPane
269 246
                             key = { type }
270 247
                             onClick = { this._onPreviewClick }
271 248
                             onDoubleClick = { this._onCloseModal }
272 249
                             selectedSourceId = { selectedSource.id }
273
-                            sources = { sources[type] || [] }
250
+                            sources = { sources[type] }
274 251
                             type = { type } />,
275 252
                         defaultSelected,
276 253
                         label: t(label)
@@ -288,6 +265,7 @@ class DesktopPicker extends Component {
288 265
      */
289 266
     _startPolling() {
290 267
         this._stopPolling();
268
+        this._updateSources();
291 269
         this._poller = window.setInterval(this._updateSources, UPDATE_INTERVAL);
292 270
     }
293 271
 
@@ -311,28 +289,35 @@ class DesktopPicker extends Component {
311 289
      * @returns {void}
312 290
      */
313 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,3 +1,4 @@
1
+import Spinner from '@atlaskit/spinner';
1 2
 import PropTypes from 'prop-types';
2 3
 import React, { Component } from 'react';
3 4
 
@@ -60,7 +61,7 @@ class DesktopPickerPane extends Component {
60 61
         const classNames
61 62
             = `desktop-picker-pane default-scrollbar source-type-${type}`;
62 63
         const previews
63
-            = sources.map(
64
+            = sources ? sources.map(
64 65
                 source =>
65 66
 
66 67
                     // eslint-disable-next-line react/jsx-wrap-multilines
@@ -70,7 +71,14 @@ class DesktopPickerPane extends Component {
70 71
                         onDoubleClick = { onDoubleClick }
71 72
                         selected = { source.id === selectedSourceId }
72 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 83
         return (
76 84
             <div className = { classNames }>

+ 73
- 0
react/features/desktop-picker/functions.js Прегледај датотеку

@@ -0,0 +1,73 @@
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,5 +1,2 @@
1 1
 export * from './actions';
2
-export * from './actionTypes';
3 2
 export * from './components';
4
-
5
-import './reducer';

+ 0
- 62
react/features/desktop-picker/reducer.js Прегледај датотеку

@@ -1,62 +0,0 @@
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…
Откажи
Сачувај