Selaa lähdekoodia

feat(invite): Add conference id to dial-in numbers display

DialInNumbersForm has been modified to display a conference id to be used for
dialing into the conference. The changes include:
- Requesting the conference id and adding the conference id to the redux store
- Displaying the conference id in DialInNumbersForm
- Modifying the copy behavior to support copying the new message to clipboard
- DialInNumbersForm does not display until all ajax requests have completed
  successfully. This eliminates the need for the REQUESTING state.
j8
Leonard Kim 8 vuotta sitten
vanhempi
commit
47c07c2e76

+ 13
- 0
css/modals/invite/_invite.scss Näytä tiedosto

@@ -8,6 +8,19 @@
8 8
 
9 9
 .invite-dialog {
10 10
     .dial-in-numbers {
11
+        .dial-in-numbers-copy {
12
+            opacity: 0;
13
+            pointer-events: none;
14
+            position: fixed;
15
+            user-select: text;
16
+            -webkit-user-select: text;
17
+        }
18
+
19
+        .dial-in-numbers-conference-id {
20
+            color: orange;
21
+            margin-left: 3px;
22
+        }
23
+
11 24
         .dial-in-numbers-trigger {
12 25
             position: relative;
13 26
             width: 100%;

+ 4
- 5
lang/main.json Näytä tiedosto

@@ -437,14 +437,13 @@
437 437
     },
438 438
     "invite": {
439 439
         "addPassword": "Add password",
440
-        "dialInNumbers": "Dial-in telephone numbers",
441
-        "errorFetchingNumbers": "Failed to obtain dial-in numbers",
440
+        "callNumber": "Call __number__",
441
+        "enterId": "Enter Meeting ID: __meetingId__ following by # to dial in from a phone",
442
+        "howToDialIn": "To dial in, use one of the following numbers and meeting ID",
442 443
         "hidePassword": "Hide password",
443 444
         "inviteTo": "Invite people to __conferenceName__",
444
-        "loadingNumbers": "Loading...",
445
+        "invitedYouTo": "__userName__ has invited you to the __meetingUrl__ conference",
445 446
         "locked": "This call is locked. New callers must have the link and enter the password to join.",
446
-        "noNumbers": "No numbers available",
447
-        "numbersDisabled": "Dialing in has been disabled",
448 447
         "showPassword": "Show password",
449 448
         "unlocked": "This call is unlocked. Any new caller with the link may join the call."
450 449
     }

+ 0
- 11
react/features/invite/actionTypes.js Näytä tiedosto

@@ -12,17 +12,6 @@ import { Symbol } from '../base/react';
12 12
 export const UPDATE_DIAL_IN_NUMBERS_FAILED
13 13
     = Symbol('UPDATE_DIAL_IN_NUMBERS_FAILED');
14 14
 
15
-/**
16
- * The type of the action which signals a request for dial-in numbers has been
17
- * started.
18
- *
19
- * {
20
- *     type: UPDATE_DIAL_IN_NUMBERS_REQUEST
21
- * }
22
- */
23
-export const UPDATE_DIAL_IN_NUMBERS_REQUEST
24
-    = Symbol('UPDATE_DIAL_IN_NUMBERS_REQUEST');
25
-
26 15
 /**
27 16
  * The type of the action which signals a request for dial-in numbers has
28 17
  * succeeded.

+ 36
- 21
react/features/invite/actions.js Näytä tiedosto

@@ -2,7 +2,6 @@ import { openDialog } from '../../features/base/dialog';
2 2
 
3 3
 import {
4 4
     UPDATE_DIAL_IN_NUMBERS_FAILED,
5
-    UPDATE_DIAL_IN_NUMBERS_REQUEST,
6 5
     UPDATE_DIAL_IN_NUMBERS_SUCCESS
7 6
 } from './actionTypes';
8 7
 import { InviteDialog } from './components';
@@ -11,6 +10,10 @@ declare var $: Function;
11 10
 declare var APP: Object;
12 11
 declare var config: Object;
13 12
 
13
+const CONFERENCE_ID_ENDPOINT = config.conferenceMapperUrl;
14
+const DIAL_IN_NUMBERS_ENDPOINT = config.dialInNumbersUrl;
15
+const MUC_URL = config && config.hosts && config.hosts.muc;
16
+
14 17
 /**
15 18
  * Opens the Invite Dialog.
16 19
  *
@@ -18,34 +21,46 @@ declare var config: Object;
18 21
  */
19 22
 export function openInviteDialog() {
20 23
     return openDialog(InviteDialog, {
21
-        conferenceUrl: encodeURI(APP.ConferenceUrl.getInviteUrl()),
22
-        dialInNumbersUrl: config.dialInNumbersUrl
24
+        conferenceUrl: encodeURI(APP.ConferenceUrl.getInviteUrl())
23 25
     });
24 26
 }
25 27
 
26 28
 /**
27
- * Sends an ajax request for dial-in numbers.
29
+ * Sends an ajax requests for dial-in numbers and conference id.
28 30
  *
29
- * @param {string} dialInNumbersUrl - The endpoint for retrieving json that
30
- * includes numbers for dialing in to a conference.
31 31
  * @returns {Function}
32 32
  */
33
-export function updateDialInNumbers(dialInNumbersUrl) {
34
-    return dispatch => {
35
-        dispatch({
36
-            type: UPDATE_DIAL_IN_NUMBERS_REQUEST
33
+export function updateDialInNumbers() {
34
+    return (dispatch, getState) => {
35
+
36
+        if (!CONFERENCE_ID_ENDPOINT || !DIAL_IN_NUMBERS_ENDPOINT || !MUC_URL) {
37
+            return;
38
+        }
39
+
40
+        const { room } = getState()['features/base/conference'];
41
+        const conferenceIdUrl
42
+            = `${CONFERENCE_ID_ENDPOINT}?conference=${room}@${MUC_URL}`;
43
+
44
+        Promise.all([
45
+            $.getJSON(DIAL_IN_NUMBERS_ENDPOINT),
46
+            $.getJSON(conferenceIdUrl)
47
+        ]).then(([ numbersResponse, idResponse ]) => {
48
+            if (!idResponse.conference || !idResponse.id) {
49
+                return Promise.reject(idResponse.message);
50
+            }
51
+
52
+            dispatch({
53
+                type: UPDATE_DIAL_IN_NUMBERS_SUCCESS,
54
+                conferenceId: idResponse,
55
+                dialInNumbers: numbersResponse
56
+            });
57
+        })
58
+        .catch(error => {
59
+            dispatch({
60
+                type: UPDATE_DIAL_IN_NUMBERS_FAILED,
61
+                error
62
+            });
37 63
         });
38 64
 
39
-        $.getJSON(dialInNumbersUrl)
40
-            .success(response =>
41
-                dispatch({
42
-                    type: UPDATE_DIAL_IN_NUMBERS_SUCCESS,
43
-                    response
44
-                }))
45
-            .error(error =>
46
-                dispatch({
47
-                    type: UPDATE_DIAL_IN_NUMBERS_FAILED,
48
-                    error
49
-                }));
50 65
     };
51 66
 }

+ 78
- 75
react/features/invite/components/DialInNumbersForm.js Näytä tiedosto

@@ -4,6 +4,7 @@ import React, { Component } from 'react';
4 4
 import { connect } from 'react-redux';
5 5
 
6 6
 import { translate } from '../../base/i18n';
7
+import { getLocalParticipant } from '../../base/participants';
7 8
 
8 9
 import { updateDialInNumbers } from '../actions';
9 10
 
@@ -25,15 +26,20 @@ class DialInNumbersForm extends Component {
25 26
      * @static
26 27
      */
27 28
     static propTypes = {
29
+        /**
30
+         * The name of the current user.
31
+         */
32
+        _currentUserName: React.PropTypes.string,
33
+
28 34
         /**
29 35
          * The redux state representing the dial-in numbers feature.
30 36
          */
31 37
         _dialIn: React.PropTypes.object,
32 38
 
33 39
         /**
34
-         * The url for retrieving dial-in numbers.
40
+         * The url for the JitsiConference.
35 41
          */
36
-        dialInNumbersUrl: React.PropTypes.string,
42
+        conferenceUrl: React.PropTypes.string,
37 43
 
38 44
         /**
39 45
          * Invoked to send an ajax request for dial-in numbers.
@@ -77,35 +83,32 @@ class DialInNumbersForm extends Component {
77 83
 
78 84
         /**
79 85
          * The internal reference to the DOM/HTML element backing the React
80
-         * {@code Component} input. It is necessary for the implementation of
81
-         * copying to the clipboard.
86
+         * {@code Component} text area. It is necessary for the implementation
87
+         * of copying to the clipboard.
82 88
          *
83 89
          * @private
84
-         * @type {HTMLInputElement}
90
+         * @type {HTMLTextAreaElement}
85 91
          */
86
-        this._inputElement = null;
92
+        this._copyElement = null;
87 93
 
88 94
         // Bind event handlers so they are only bound once for every instance.
89
-        this._onClick = this._onClick.bind(this);
95
+        this._onCopyClick = this._onCopyClick.bind(this);
90 96
         this._onOpenChange = this._onOpenChange.bind(this);
91 97
         this._onSelect = this._onSelect.bind(this);
92
-        this._setInput = this._setInput.bind(this);
98
+        this._setCopyElement = this._setCopyElement.bind(this);
93 99
     }
94 100
 
95 101
     /**
96
-     * Dispatches a request for numbers if not already present in the redux
97
-     * store. If numbers are present, sets a default number to display in the
98
-     * dropdown trigger.
102
+     * Sets a default number to display in the dropdown trigger.
99 103
      *
100 104
      * @inheritdoc
101 105
      * returns {void}
102 106
      */
103
-    componentDidMount() {
107
+    componentWillMount() {
104 108
         if (this.props._dialIn.numbers) {
105 109
             this._setDefaultNumber(this.props._dialIn.numbers);
106 110
         } else {
107
-            this.props.dispatch(
108
-                updateDialInNumbers(this.props.dialInNumbersUrl));
111
+            this.props.dispatch(updateDialInNumbers());
109 112
         }
110 113
     }
111 114
 
@@ -123,52 +126,46 @@ class DialInNumbersForm extends Component {
123 126
     }
124 127
 
125 128
     /**
126
-     * Implements React's {@link Component#render()}.
129
+     * Implements React's {@link Component#render()}. Returns null if the
130
+     * component is not ready for display.
127 131
      *
128 132
      * @inheritdoc
129
-     * @returns {ReactElement}
133
+     * @returns {ReactElement|null}
130 134
      */
131 135
     render() {
132
-        const { t, _dialIn } = this.props;
133
-
134
-        const numbers = _dialIn.numbers;
135
-        const items = numbers ? this._formatNumbers(numbers) : [];
136
+        const { _dialIn, t } = this.props;
137
+        const { conferenceId, numbers, numbersEnabled } = _dialIn;
138
+        const { selectedNumber } = this.state;
136 139
 
137
-        const isEnabled = this._isDropdownEnabled();
138
-        const inputWrapperClassNames
139
-            = `form-control__container ${isEnabled ? '' : 'is-disabled'}
140
-                ${_dialIn.loading ? 'is-loading' : ''}`;
141
-
142
-        let triggerText = '';
143
-
144
-        if (!_dialIn.numbersEnabled) {
145
-            triggerText = t('invite.numbersDisabled');
146
-        } else if (this.state.selectedNumber
147
-            && this.state.selectedNumber.content) {
148
-            triggerText = this.state.selectedNumber.content;
149
-        } else if (!numbers && _dialIn.loading) {
150
-            triggerText = t('invite.loadingNumbers');
151
-        } else if (_dialIn.error) {
152
-            triggerText = t('invite.errorFetchingNumbers');
153
-        } else {
154
-            triggerText = t('invite.noNumbers');
140
+        if (!conferenceId || !numbers || !numbersEnabled || !selectedNumber) {
141
+            return null;
155 142
         }
156 143
 
144
+        const items = numbers ? this._formatNumbers(numbers) : [];
145
+
157 146
         return (
158 147
             <div className = 'form-control dial-in-numbers'>
159 148
                 <label className = 'form-control__label'>
160
-                    { t('invite.dialInNumbers') }
149
+                    { t('invite.howToDialIn') }
150
+                    <span className = 'dial-in-numbers-conference-id'>
151
+                        { conferenceId }
152
+                    </span>
161 153
                 </label>
162
-                <div className = { inputWrapperClassNames }>
163
-                    { this._createDropdownMenu(items, triggerText) }
154
+                <div className = 'form-control__container'>
155
+                    { this._createDropdownMenu(items, selectedNumber.content) }
164 156
                     <button
165 157
                         className = 'button-control button-control_light'
166
-                        disabled = { !isEnabled }
167
-                        onClick = { this._onClick }
158
+                        onClick = { this._onCopyClick }
168 159
                         type = 'button'>
169 160
                         Copy
170 161
                     </button>
171 162
                 </div>
163
+                <textarea
164
+                    className = 'dial-in-numbers-copy'
165
+                    readOnly = { true }
166
+                    ref = { this._setCopyElement }
167
+                    tabIndex = '-1'
168
+                    value = { this._generateCopyText() } />
172 169
             </div>
173 170
         );
174 171
     }
@@ -209,7 +206,6 @@ class DialInNumbersForm extends Component {
209 206
                 <input
210 207
                     className = 'input-control'
211 208
                     readOnly = { true }
212
-                    ref = { this._setInput }
213 209
                     type = 'text'
214 210
                     value = { triggerText || '' } />
215 211
                 <span className = 'dial-in-numbers-trigger-icon'>
@@ -288,19 +284,26 @@ class DialInNumbersForm extends Component {
288 284
     }
289 285
 
290 286
     /**
291
-     * Determines if the dropdown can be opened.
287
+     * Creates a message describing how to dial in to the conference.
292 288
      *
293 289
      * @private
294
-     * @returns {boolean} True if the dropdown can be opened.
290
+     * @returns {string}
295 291
      */
296
-    _isDropdownEnabled() {
297
-        const { selectedNumber } = this.state;
292
+    _generateCopyText() {
293
+        const welcome = this.props.t('invite.invitedYouTo', {
294
+            meetingUrl: this.props.conferenceUrl,
295
+            userName: this.props._currentUserName
296
+        });
298 297
 
299
-        return Boolean(
300
-            this.props._dialIn.numbersEnabled
301
-            && selectedNumber
302
-            && selectedNumber.content
303
-        );
298
+        const callNumber = this.props.t('invite.callNumber',
299
+            { number: this.state.selectedNumber.number });
300
+        const stepOne = `1) ${callNumber}`;
301
+
302
+        const enterId = this.props.t('invite.enterId',
303
+            { meetingId: this.props._dialIn.conferenceId });
304
+        const stepTwo = `2) ${enterId}`;
305
+
306
+        return `${welcome}\n${stepOne}\n${stepTwo}`;
304 307
     }
305 308
 
306 309
     /**
@@ -311,16 +314,11 @@ class DialInNumbersForm extends Component {
311 314
      * @private
312 315
      * @returns {void}
313 316
      */
314
-    _onClick() {
315
-        const displayedValue = this.state.selectedNumber.content;
316
-        const desiredNumber = this.state.selectedNumber.number;
317
-        const startIndex = displayedValue.indexOf(desiredNumber);
318
-
317
+    _onCopyClick() {
319 318
         try {
320
-            this._input.focus();
321
-            this._input.setSelectionRange(startIndex, displayedValue.length);
319
+            this._copyElement.select();
322 320
             document.execCommand('copy');
323
-            this._input.blur();
321
+            this._copyElement.blur();
324 322
         } catch (err) {
325 323
             logger.error('error when copying the text', err);
326 324
         }
@@ -337,7 +335,7 @@ class DialInNumbersForm extends Component {
337 335
      */
338 336
     _onOpenChange(dropdownEvent) {
339 337
         this.setState({
340
-            isDropdownOpen: this._isDropdownEnabled() && dropdownEvent.isOpen
338
+            isDropdownOpen: dropdownEvent.isOpen
341 339
         });
342 340
     }
343 341
 
@@ -355,6 +353,19 @@ class DialInNumbersForm extends Component {
355 353
         });
356 354
     }
357 355
 
356
+    /**
357
+     * Sets the internal reference to the DOM/HTML element backing the React
358
+     * {@code Component} text area.
359
+     *
360
+     * @param {HTMLTextAreaElement} element - The DOM/HTML element for this
361
+     * {@code Component}'s text area.
362
+     * @private
363
+     * @returns {void}
364
+     */
365
+    _setCopyElement(element) {
366
+        this._copyElement = element;
367
+    }
368
+
358 369
     /**
359 370
      * Updates the internal state of the currently selected number by defaulting
360 371
      * to the first available number.
@@ -370,19 +381,6 @@ class DialInNumbersForm extends Component {
370 381
             selectedNumber: numbers[0]
371 382
         });
372 383
     }
373
-
374
-    /**
375
-     * Sets the internal reference to the DOM/HTML element backing the React
376
-     * {@code Component} input.
377
-     *
378
-     * @param {HTMLInputElement} element - The DOM/HTML element for this
379
-     * {@code Component}'s input.
380
-     * @private
381
-     * @returns {void}
382
-     */
383
-    _setInput(element) {
384
-        this._input = element;
385
-    }
386 384
 }
387 385
 
388 386
 /**
@@ -392,11 +390,16 @@ class DialInNumbersForm extends Component {
392 390
  * @param {Object} state - The Redux state.
393 391
  * @private
394 392
  * @returns {{
393
+ *     _currentUserName: React.PropTypes.string,
395 394
  *     _dialIn: React.PropTypes.object
396 395
  * }}
397 396
  */
398 397
 function _mapStateToProps(state) {
398
+    const { name }
399
+        = getLocalParticipant(state['features/base/participants']);
400
+
399 401
     return {
402
+        _currentUserName: name,
400 403
         _dialIn: state['features/invite/dial-in']
401 404
     };
402 405
 }

+ 3
- 24
react/features/invite/components/InviteDialog.js Näytä tiedosto

@@ -41,11 +41,6 @@ class InviteDialog extends Component {
41 41
          */
42 42
         conferenceUrl: React.PropTypes.string,
43 43
 
44
-        /**
45
-         * The url for retrieving dial-in numbers.
46
-         */
47
-        dialInNumbersUrl: React.PropTypes.string,
48
-
49 44
         /**
50 45
          * Invoked to obtain translated strings.
51 46
          */
@@ -68,7 +63,7 @@ class InviteDialog extends Component {
68 63
      * @returns {ReactElement}
69 64
      */
70 65
     render() {
71
-        const { _conference } = this.props;
66
+        const { _conference, conferenceUrl } = this.props;
72 67
         const titleString
73 68
             = this.props.t(
74 69
                 'invite.inviteTo',
@@ -80,8 +75,8 @@ class InviteDialog extends Component {
80 75
                 okTitleKey = 'dialog.done'
81 76
                 titleString = { titleString }>
82 77
                 <div className = 'invite-dialog'>
83
-                    <ShareLinkForm toCopy = { this.props.conferenceUrl } />
84
-                    { this._renderDialInNumbersForm() }
78
+                    <ShareLinkForm toCopy = { conferenceUrl } />
79
+                    <DialInNumbersForm conferenceUrl = { conferenceUrl } />
85 80
                     <PasswordContainer
86 81
                         conference = { _conference.conference }
87 82
                         locked = { _conference.locked }
@@ -91,22 +86,6 @@ class InviteDialog extends Component {
91 86
             </Dialog>
92 87
         );
93 88
     }
94
-
95
-    /**
96
-     * Creates a React {@code Component} for displaying and copying to clipboard
97
-     * telephone numbers for dialing in to the conference.
98
-     *
99
-     * @private
100
-     * @returns {ReactElement|null}
101
-     */
102
-    _renderDialInNumbersForm() {
103
-        return (
104
-            this.props.dialInNumbersUrl
105
-                ? <DialInNumbersForm
106
-                    dialInNumbersUrl = { this.props.dialInNumbersUrl } />
107
-                : null
108
-        );
109
-    }
110 89
 }
111 90
 
112 91
 /**

+ 3
- 12
react/features/invite/reducer.js Näytä tiedosto

@@ -4,7 +4,6 @@ import {
4 4
 
5 5
 import {
6 6
     UPDATE_DIAL_IN_NUMBERS_FAILED,
7
-    UPDATE_DIAL_IN_NUMBERS_REQUEST,
8 7
     UPDATE_DIAL_IN_NUMBERS_SUCCESS
9 8
 } from './actionTypes';
10 9
 
@@ -19,24 +18,16 @@ ReducerRegistry.register(
19 18
         case UPDATE_DIAL_IN_NUMBERS_FAILED: {
20 19
             return {
21 20
                 ...state,
22
-                error: action.error,
23
-                loading: false
21
+                error: action.error
24 22
             };
25 23
         }
26 24
 
27
-        case UPDATE_DIAL_IN_NUMBERS_REQUEST: {
28
-            return {
29
-                ...state,
30
-                error: null,
31
-                loading: true
32
-            };
33
-        }
34 25
         case UPDATE_DIAL_IN_NUMBERS_SUCCESS: {
35
-            const { numbers, numbersEnabled } = action.response;
26
+            const { numbers, numbersEnabled } = action.dialInNumbers;
36 27
 
37 28
             return {
29
+                conferenceId: action.conferenceId.id,
38 30
                 error: null,
39
-                loading: false,
40 31
                 numbers,
41 32
                 numbersEnabled
42 33
             };

Loading…
Peruuta
Tallenna