소스 검색

feat(info): new dialog design (#2452)

* feat(info): new dialog design

- Add display of a dial in number.
- Add a static page to show a full list of dial in numbers.
- Add password management.
- Invite modal will be changed soon to remove password and
  dial-in.

* squash: add classes for torture tests

* squash: class for local lock for torture tests

* squash: more classes for torture tests

* squash: more classes, work around linter

* squash: remove unused string?

* squash: work around linter and avoid react warnings

* squash: pixel push, add bold

* squash: font size bump

* squash: NumbersTable -> NumbersList

* squash: document response from fetching numbers

* squash: showEdit -> editEnabled, pixel push padding for alignment

* squash: pin -> conferenceID

* squash: prepare to receive defaultCountry from api
master
virtuacoplenny 7 년 전
부모
커밋
59d046dca9
25개의 변경된 파일1274개의 추가작업 그리고 205개의 파일을 삭제
  1. 2
    0
      Makefile
  2. 68
    1
      css/modals/invite/_info.scss
  3. 16
    3
      lang/main.json
  4. 1
    0
      package.json
  5. 0
    199
      react/features/invite/components/InfoDialog.web.js
  6. 1
    1
      react/features/invite/components/InfoDialogButton.web.js
  7. 0
    0
      react/features/invite/components/dial-in-info-page/ConferenceID.native.js
  8. 46
    0
      react/features/invite/components/dial-in-info-page/ConferenceID.web.js
  9. 0
    0
      react/features/invite/components/dial-in-info-page/DialInInfoApp.native.js
  10. 24
    0
      react/features/invite/components/dial-in-info-page/DialInInfoApp.web.js
  11. 0
    0
      react/features/invite/components/dial-in-info-page/DialInInfoPage.native.js
  12. 220
    0
      react/features/invite/components/dial-in-info-page/DialInInfoPage.web.js
  13. 0
    0
      react/features/invite/components/dial-in-info-page/NumbersList.native.js
  14. 120
    0
      react/features/invite/components/dial-in-info-page/NumbersList.web.js
  15. 1
    0
      react/features/invite/components/dial-in-info-page/index.js
  16. 0
    0
      react/features/invite/components/info-dialog/DialInNumber.native.js
  17. 60
    0
      react/features/invite/components/info-dialog/DialInNumber.web.js
  18. 0
    0
      react/features/invite/components/info-dialog/InfoDialog.native.js
  19. 503
    0
      react/features/invite/components/info-dialog/InfoDialog.web.js
  20. 0
    0
      react/features/invite/components/info-dialog/PasswordForm.native.js
  21. 175
    0
      react/features/invite/components/info-dialog/PasswordForm.web.js
  22. 1
    0
      react/features/invite/components/info-dialog/index.js
  23. 7
    1
      react/features/invite/reducer.js
  24. 17
    0
      static/dialInInfo.html
  25. 12
    0
      webpack.config.js

+ 2
- 0
Makefile 파일 보기

@@ -33,6 +33,8 @@ deploy-appbundle:
33 33
 		$(BUILD_DIR)/external_api.min.map \
34 34
 		$(BUILD_DIR)/device_selection_popup_bundle.min.js \
35 35
 		$(BUILD_DIR)/device_selection_popup_bundle.min.map \
36
+		$(BUILD_DIR)/dial_in_info_bundle.min.js \
37
+		$(BUILD_DIR)/dial_in_info_bundle.min.map \
36 38
 		$(BUILD_DIR)/alwaysontop.min.js \
37 39
 		$(BUILD_DIR)/alwaysontop.min.map \
38 40
 		$(OUTPUT_DIR)/analytics-ga.js \

+ 68
- 1
css/modals/invite/_info.scss 파일 보기

@@ -4,16 +4,20 @@
4 4
 
5 5
     .info-dialog-action-link {
6 6
         display: inline-block;
7
+        line-height: 1.5em;
7 8
 
8 9
         a {
9 10
             cursor: pointer;
11
+            vertical-align: middle;
10 12
         }
11 13
     }
12 14
 
13 15
     .info-dialog-action-link:before {
14 16
         color: $linkFontColor;
15 17
         content: '\2022';
18
+        font-size: 1.5em;
16 19
         padding: 0 10px;
20
+        vertical-align: middle;
17 21
     }
18 22
 
19 23
     .info-dialog-action-link:first-child:before {
@@ -22,6 +26,8 @@
22 26
     }
23 27
 
24 28
     .info-dialog-action-links {
29
+        font-weight: bold;
30
+        margin-top: 10px;
25 31
         white-space: nowrap;
26 32
     }
27 33
 
@@ -39,16 +45,27 @@
39 45
 
40 46
     .info-dialog-column {
41 47
         margin-right: 10px;
48
+        overflow: hidden;
49
+
50
+        a,
51
+        a:active,
52
+        a:focus,
53
+        a:hover {
54
+            text-decoration: none;
55
+        }
42 56
     }
43 57
 
44 58
     .info-dialog-conference-url {
45
-        margin: 10px 0;
46 59
         max-width: 250px;
47 60
         overflow: hidden;
48 61
         text-overflow: ellipsis;
49 62
         white-space: nowrap;
50 63
     }
51 64
 
65
+    .info-dialog-dial-in {
66
+        white-space: nowrap;
67
+    }
68
+
52 69
     .info-dialog-icon {
53 70
         color: #6453C0;
54 71
         font-size: 16px;
@@ -56,5 +73,55 @@
56 73
 
57 74
     .info-dialog-title {
58 75
         font-weight: bold;
76
+        margin-bottom: 10px;
77
+    }
78
+
79
+    .info-password,
80
+    .info-dialog-password,
81
+    .info-password-form {
82
+        display: flex;
83
+    }
84
+
85
+    .info-password-field {
86
+        margin-left: 2px;
87
+        overflow: hidden;
88
+        text-overflow: ellipsis;
89
+        white-space: nowrap;
90
+    }
91
+
92
+    .info-password-none,
93
+    .info-password-remote {
94
+        opacity: 0.5;
95
+    }
96
+
97
+    .info-password-input {
98
+        background-color: transparent;
99
+        border: none;
100
+        color: inherit;
101
+        padding-left: 0;
102
+    }
103
+
104
+    .conference-id {
105
+        margin-left: 5px;
106
+    }
107
+}
108
+
109
+.dial-in-page {
110
+    align-items: center;
111
+    display: flex;
112
+    flex-direction: column;
113
+    font-size: 24px;
114
+    height: 100%;
115
+    justify-content: center;
116
+    width: 100%;
117
+
118
+    .dial-in-numbers-list {
119
+        font-size: 24px;
120
+        margin-top: 20px;
121
+    }
122
+
123
+    .dial-in-conference-id {
124
+        text-align: center;
125
+        width: 30%;
59 126
     }
60 127
 }

+ 16
- 3
lang/main.json 파일 보기

@@ -533,9 +533,22 @@
533 533
         "veryGood": "Very Good"
534 534
     },
535 535
     "info": {
536
-        "copy": "Copy link",
537
-        "invite": "Invite in __app__",
538
-        "title": "Call access info",
536
+        "cancelPassword": "Cancel password",
537
+        "conferenceURL": "Link: __url__",
538
+        "country": "Country",
539
+        "dialANumber": "To join your meeting, dial one of these numbers and then enter this PIN: __conferenceID__#",
540
+        "dialInNumber": "Dial-in: __phoneNumber__",
541
+        "dialInConferenceID": "PIN: __conferenceID__#",
542
+        "dialInNotSupported": "Sorry, dialing in is currently not suppported.",
543
+        "genericError": "Whoops, something went wrong.",
544
+        "invitePhone": "To join by phone, dial __number__ and enter this PIN: __pin__#",
545
+        "invitePhoneAlternatives": "To view more phone numbers, click this link: __url__",
546
+        "inviteURL": "To join the video meeting, click this link: __url__",
547
+        "moreNumbers": "More numbers",
548
+        "noPassword": "None",
549
+        "numbers": "Dial-in Numbers",
550
+        "password": "Password:",
551
+        "title": "Call info",
539 552
         "tooltip": "Get access info about the meeting"
540 553
     },
541 554
     "profileModal": {

+ 1
- 0
package.json 파일 보기

@@ -100,6 +100,7 @@
100 100
     "string-replace-loader": "1.3.0",
101 101
     "style-loader": "0.19.0",
102 102
     "uglifyjs-webpack-plugin": "1.1.2",
103
+    "whatwg-fetch": "2.0.3",
103 104
     "webpack": "3.9.1",
104 105
     "webpack-dev-server": "2.9.5"
105 106
   },

+ 0
- 199
react/features/invite/components/InfoDialog.web.js 파일 보기

@@ -1,199 +0,0 @@
1
-/* global interfaceConfig */
2
-
3
-import React, { Component } from 'react';
4
-import { connect } from 'react-redux';
5
-import PropTypes from 'prop-types';
6
-
7
-import { getInviteURL } from '../../base/connection';
8
-import { openDialog } from '../../base/dialog';
9
-import { translate } from '../../base/i18n';
10
-
11
-import AddPeopleDialog from './AddPeopleDialog';
12
-
13
-const logger = require('jitsi-meet-logger').getLogger(__filename);
14
-
15
-/**
16
- * A React Component with the contents for a dialog that shows information about
17
- * the current conference and provides ways to invite other participants.
18
- *
19
- * @extends Component
20
- */
21
-class InfoDialog extends Component {
22
-    /**
23
-     * {@code InfoDialog} component's property types.
24
-     *
25
-     * @static
26
-     */
27
-    static propTypes = {
28
-        /**
29
-         * The current url of the conference to be copied onto the clipboard.
30
-         */
31
-        _inviteURL: PropTypes.string,
32
-
33
-        /**
34
-         * Whether or not the link to open the {@code AddPeopleDialog} should be
35
-         * displayed.
36
-         */
37
-        _showAddPeople: PropTypes.bool,
38
-
39
-        /**
40
-         * Invoked to open a dialog for adding participants to the conference.
41
-         */
42
-        dispatch: PropTypes.func,
43
-
44
-        /**
45
-         * Callback invoked when the dialog should be closed.
46
-         */
47
-        onClose: PropTypes.func,
48
-
49
-        /**
50
-         * Callback invoked when a mouse-related event has been detected.
51
-         */
52
-        onMouseOver: PropTypes.func,
53
-
54
-        /**
55
-         * Invoked to obtain translated strings.
56
-         */
57
-        t: PropTypes.func
58
-    };
59
-
60
-    /**
61
-     * Initializes new {@code InfoDialog} instance.
62
-     *
63
-     * @param {Object} props - The read-only properties with which the new
64
-     * instance is to be initialized.
65
-     */
66
-    constructor(props) {
67
-        super(props);
68
-
69
-        /**
70
-         * The internal reference to the DOM/HTML element backing the React
71
-         * {@code Component} input. It is necessary for the implementation
72
-         * of copying to the clipboard.
73
-         *
74
-         * @private
75
-         * @type {HTMLInputElement}
76
-         */
77
-        this._copyElement = null;
78
-
79
-        // Bind event handlers so they are only bound once for every instance.
80
-        this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
81
-        this._onOpenInviteDialog = this._onOpenInviteDialog.bind(this);
82
-        this._setCopyElement = this._setCopyElement.bind(this);
83
-    }
84
-
85
-    /**
86
-     * Implements React's {@link Component#render()}.
87
-     *
88
-     * @inheritdoc
89
-     * @returns {ReactElement}
90
-     */
91
-    render() {
92
-        return (
93
-            <div
94
-                className = 'info-dialog'
95
-                onMouseOver = { this.props.onMouseOver } >
96
-                <div className = 'info-dialog-column'>
97
-                    <h4 className = 'info-dialog-icon'>
98
-                        <i className = 'icon-info' />
99
-                    </h4>
100
-                </div>
101
-                <div className = 'info-dialog-column'>
102
-                    <div className = 'info-dialog-title'>
103
-                        { this.props.t('info.title') }
104
-                    </div>
105
-                    <div
106
-                        className = 'info-dialog-conference-url'
107
-                        ref = { this._inviteUrlElement }>
108
-                        { this.props._inviteURL }
109
-                        <input
110
-                            className = 'info-dialog-copy-element'
111
-                            readOnly = { true }
112
-                            ref = { this._setCopyElement }
113
-                            tabIndex = '-1'
114
-                            value = { this.props._inviteURL } />
115
-                    </div>
116
-                    <div className = 'info-dialog-action-links'>
117
-                        <div className = 'info-dialog-action-link'>
118
-                            <a onClick = { this._onCopyInviteURL }>
119
-                                { this.props.t('info.copy') }
120
-                            </a>
121
-                        </div>
122
-                        { this.props._showAddPeople
123
-                            ? <div className = 'info-dialog-action-link'>
124
-                                <a onClick = { this._onOpenInviteDialog }>
125
-                                    { this.props.t('info.invite', {
126
-                                        app: interfaceConfig.ADD_PEOPLE_APP_NAME
127
-                                    }) }
128
-                                </a>
129
-                            </div>
130
-                            : null }
131
-                    </div>
132
-                </div>
133
-            </div>
134
-        );
135
-    }
136
-
137
-    /**
138
-     * Callback invoked to copy the contents of {@code this._copyElement} to the
139
-     * clipboard.
140
-     *
141
-     * @private
142
-     * @returns {void}
143
-     */
144
-    _onCopyInviteURL() {
145
-        try {
146
-            this._copyElement.select();
147
-            document.execCommand('copy');
148
-            this._copyElement.blur();
149
-        } catch (err) {
150
-            logger.error('error when copying the text', err);
151
-        }
152
-    }
153
-
154
-    /**
155
-     * Callback invoked to open the {@code AddPeople} dialog.
156
-     *
157
-     * @private
158
-     * @returns {void}
159
-     */
160
-    _onOpenInviteDialog() {
161
-        this.props.dispatch(openDialog(AddPeopleDialog));
162
-
163
-        if (this.props.onClose) {
164
-            this.props.onClose();
165
-        }
166
-    }
167
-
168
-    /**
169
-     * Sets the internal reference to the DOM/HTML element backing the React
170
-     * {@code Component} input.
171
-     *
172
-     * @param {HTMLInputElement} element - The DOM/HTML element for this
173
-     * {@code Component}'s input.
174
-     * @private
175
-     * @returns {void}
176
-     */
177
-    _setCopyElement(element) {
178
-        this._copyElement = element;
179
-    }
180
-}
181
-
182
-/**
183
- * Maps (parts of) the Redux state to the associated props for the
184
- * {@code InfoDialog} component.
185
- *
186
- * @param {Object} state - The Redux state.
187
- * @private
188
- * @returns {{
189
- *     _inviteURL: string
190
- * }}
191
- */
192
-function _mapStateToProps(state) {
193
-    return {
194
-        _inviteURL: getInviteURL(state),
195
-        _showAddPeople: !state['features/base/jwt'].isGuest
196
-    };
197
-}
198
-
199
-export default translate(connect(_mapStateToProps)(InfoDialog));

+ 1
- 1
react/features/invite/components/InfoDialogButton.web.js 파일 보기

@@ -8,7 +8,7 @@ import { connect } from 'react-redux';
8 8
 import { ToolbarButton, TOOLTIP_TO_POPUP_POSITION } from '../../toolbox';
9 9
 
10 10
 import { setInfoDialogVisibility } from '../actions';
11
-import InfoDialog from './InfoDialog';
11
+import { InfoDialog } from './info-dialog';
12 12
 
13 13
 const { INITIAL_TOOLBAR_TIMEOUT } = interfaceConfig;
14 14
 

react/features/invite/components/InfoDialog.native.js → react/features/invite/components/dial-in-info-page/ConferenceID.native.js 파일 보기


+ 46
- 0
react/features/invite/components/dial-in-info-page/ConferenceID.web.js 파일 보기

@@ -0,0 +1,46 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+
4
+import { translate } from '../../../base/i18n';
5
+
6
+/**
7
+ * Displays a conference ID used as a pin for dialing into a conferene.
8
+ *
9
+ * @extends Component
10
+ */
11
+class ConferenceID extends Component {
12
+    /**
13
+     * {@code ConferenceID} component's property types.
14
+     *
15
+     * @static
16
+     */
17
+    static propTypes = {
18
+        /**
19
+         * The conference ID for dialing in.
20
+         */
21
+        conferenceID: PropTypes.number,
22
+
23
+        /**
24
+         * Invoked to obtain translated strings.
25
+         */
26
+        t: PropTypes.func
27
+    };
28
+
29
+    /**
30
+     * Implements React's {@link Component#render()}.
31
+     *
32
+     * @inheritdoc
33
+     * @returns {ReactElement}
34
+     */
35
+    render() {
36
+        const { conferenceID, t } = this.props;
37
+
38
+        return (
39
+            <div className = 'dial-in-conference-id'>
40
+                { t('info.dialANumber', { conferenceID }) }
41
+            </div>
42
+        );
43
+    }
44
+}
45
+
46
+export default translate(ConferenceID);

+ 0
- 0
react/features/invite/components/dial-in-info-page/DialInInfoApp.native.js 파일 보기


+ 24
- 0
react/features/invite/components/dial-in-info-page/DialInInfoApp.web.js 파일 보기

@@ -0,0 +1,24 @@
1
+import React from 'react';
2
+import ReactDOM from 'react-dom';
3
+import { I18nextProvider } from 'react-i18next';
4
+
5
+import parseURLParams from '../../../base/config/parseURLParams';
6
+import { i18next } from '../../../base/i18n';
7
+
8
+import DialInInfoPage from './DialInInfoPage';
9
+
10
+document.addEventListener('DOMContentLoaded', () => {
11
+    const params = parseURLParams(window.location, true, 'search');
12
+
13
+    ReactDOM.render(
14
+        <I18nextProvider i18n = { i18next }>
15
+            <DialInInfoPage
16
+                room = { params.room } />
17
+        </I18nextProvider>,
18
+        document.getElementById('react')
19
+    );
20
+});
21
+
22
+window.addEventListener('beforeunload', () => {
23
+    ReactDOM.unmountComponentAtNode(document.getElementById('react'));
24
+});

+ 0
- 0
react/features/invite/components/dial-in-info-page/DialInInfoPage.native.js 파일 보기


+ 220
- 0
react/features/invite/components/dial-in-info-page/DialInInfoPage.web.js 파일 보기

@@ -0,0 +1,220 @@
1
+/* global config */
2
+
3
+import PropTypes from 'prop-types';
4
+import React, { Component } from 'react';
5
+
6
+import { translate } from '../../../base/i18n';
7
+
8
+import ConferenceID from './ConferenceID';
9
+import NumbersList from './NumbersList';
10
+
11
+/**
12
+ * Displays a page listing numbers for dialing into a conference and pin to
13
+ * the a specific conference.
14
+ *
15
+ * @extends Component
16
+ */
17
+class DialInInfoPage extends Component {
18
+    /**
19
+     * {@code DialInInfoPage} component's property types.
20
+     *
21
+     * @static
22
+     */
23
+    static propTypes = {
24
+        /**
25
+         * The name of the conference to show a conferenceID for.
26
+         */
27
+        room: PropTypes.string,
28
+
29
+        /**
30
+         * Invoked to obtain translated strings.
31
+         */
32
+        t: PropTypes.func
33
+    };
34
+
35
+    /**
36
+     * {@code DialInInfoPage} component's local state.
37
+     *
38
+     * @type {Object}
39
+     * @property {number} conferenceID - The numeric ID of the conference, used
40
+     * as a pin when dialing in.
41
+     * @property {string} error - An error message to display.
42
+     * @property {boolean} loading - Whether or not the app is fetching data.
43
+     * @property {Array|Object} numbers - The dial-in numbers.
44
+     * entered by the local participant.
45
+     * @property {boolean} numbersEnabled - Whether or not dial-in is allowed.
46
+     */
47
+    state = {
48
+        conferenceID: null,
49
+        error: '',
50
+        loading: true,
51
+        numbers: null,
52
+        numbersEnabled: null
53
+    };
54
+
55
+    /**
56
+     * Initializes a new {@code DialInInfoPage} instance.
57
+     *
58
+     * @param {Object} props - The read-only properties with which the new
59
+     * instance is to be initialized.
60
+     */
61
+    constructor(props) {
62
+        super(props);
63
+
64
+        // Bind event handlers so they are only bound once for every instance.
65
+        this._onGetNumbersSuccess = this._onGetNumbersSuccess.bind(this);
66
+        this._onGetConferenceIDSuccess
67
+            = this._onGetConferenceIDSuccess.bind(this);
68
+        this._setErrorMessage = this._setErrorMessage.bind(this);
69
+    }
70
+
71
+    /**
72
+     * Implements {@link Component#componentDidMount()}. Invoked immediately
73
+     * after this component is mounted.
74
+     *
75
+     * @inheritdoc
76
+     * @returns {void}
77
+     */
78
+    componentDidMount() {
79
+        const getNumbers = this._getNumbers()
80
+            .then(this._onGetNumbersSuccess)
81
+            .catch(this._setErrorMessage);
82
+
83
+        const getID = this._getConferenceID()
84
+            .then(this._onGetConferenceIDSuccess)
85
+            .catch(this._setErrorMessage);
86
+
87
+        Promise.all([ getNumbers, getID ])
88
+            .then(() => {
89
+                this.setState({ loading: false });
90
+            });
91
+    }
92
+
93
+    /**
94
+     * Implements React's {@link Component#render()}.
95
+     *
96
+     * @inheritdoc
97
+     * @returns {ReactElement}
98
+     */
99
+    render() {
100
+        let contents;
101
+
102
+        const { conferenceID, error, loading, numbersEnabled } = this.state;
103
+
104
+        if (loading) {
105
+            contents = '';
106
+        } else if (numbersEnabled === false) {
107
+            contents = this.props.t('invite.disabled');
108
+        } else if (error) {
109
+            contents = error;
110
+        } else {
111
+            contents = [
112
+                conferenceID
113
+                    ? <ConferenceID
114
+                        conferenceID = { conferenceID }
115
+                        key = 'conferenceID' />
116
+                    : null,
117
+                <NumbersList
118
+                    key = 'numbers'
119
+                    numbers = { this.state.numbers } />
120
+            ];
121
+        }
122
+
123
+        return (
124
+            <div className = 'dial-in-page'>
125
+                { contents }
126
+            </div>
127
+        );
128
+    }
129
+
130
+    /**
131
+     * Creates an AJAX request for the conference ID.
132
+     *
133
+     * @private
134
+     * @returns {Promise}
135
+     */
136
+    _getConferenceID() {
137
+        const { room } = this.props;
138
+        const { dialInConfCodeUrl, hosts } = config;
139
+        const mucURL = hosts && hosts.muc;
140
+
141
+        if (!dialInConfCodeUrl || !mucURL || !room) {
142
+            return Promise.resolve();
143
+        }
144
+
145
+        const conferenceIDURL
146
+            = `${dialInConfCodeUrl}?conference=${room}@${mucURL}`;
147
+
148
+        return fetch(conferenceIDURL)
149
+            .then(response => response.json())
150
+            .catch(() => Promise.reject(this.props.t('info.genericError')));
151
+    }
152
+
153
+    /**
154
+     * Creates an AJAX request for dial-in numbers.
155
+     *
156
+     * @private
157
+     * @returns {Promise}
158
+     */
159
+    _getNumbers() {
160
+        const { dialInNumbersUrl } = config;
161
+
162
+        if (!dialInNumbersUrl) {
163
+            return Promise.reject(this.props.t('info.dialInNotSupported'));
164
+        }
165
+
166
+        return fetch(dialInNumbersUrl)
167
+            .then(response => response.json())
168
+            .catch(() => Promise.reject(this.props.t('info.genericError')));
169
+    }
170
+
171
+    /**
172
+     * Callback invoked when fetching the conference ID succeeds.
173
+     *
174
+     * @param {Object} response - The response from fetching the conference ID.
175
+     * @private
176
+     * @returns {void}
177
+     */
178
+    _onGetConferenceIDSuccess(response = {}) {
179
+        const { conference, id } = response;
180
+
181
+        if (!conference || !id) {
182
+            return;
183
+        }
184
+
185
+        this.setState({ conferenceID: id });
186
+    }
187
+
188
+    /**
189
+     * Callback invoked when fetching dial-in numbers succeeds. Sets the
190
+     * internal to show the numbers.
191
+     *
192
+     * @param {Object} response - The response from fetching dial-in numbers.
193
+     * @param {Array|Object} response.numbers - The dial-in numbers.
194
+     * @param {boolean} reponse.numbersEnabled - Whether or not dial-in is
195
+     * enabled.
196
+     * @private
197
+     * @returns {void}
198
+     */
199
+    _onGetNumbersSuccess({ numbers, numbersEnabled }) {
200
+        this.setState({
201
+            numbersEnabled,
202
+            numbers
203
+        });
204
+    }
205
+
206
+    /**
207
+     * Sets an error message to display on the page instead of content.
208
+     *
209
+     * @param {string} error - The error message to display.
210
+     * @private
211
+     * @returns {void}
212
+     */
213
+    _setErrorMessage(error) {
214
+        this.setState({
215
+            error
216
+        });
217
+    }
218
+}
219
+
220
+export default translate(DialInInfoPage);

+ 0
- 0
react/features/invite/components/dial-in-info-page/NumbersList.native.js 파일 보기


+ 120
- 0
react/features/invite/components/dial-in-info-page/NumbersList.web.js 파일 보기

@@ -0,0 +1,120 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+
4
+import { translate } from '../../../base/i18n';
5
+
6
+/**
7
+ * Displays a table with phone numbers to dial in to a conference.
8
+ *
9
+ * @extends Component
10
+ */
11
+class NumbersList extends Component {
12
+    /**
13
+     * {@code NumbersList} component's property types.
14
+     *
15
+     * @static
16
+     */
17
+    static propTypes = {
18
+        /**
19
+         * The phone numbers to display. Can be an array of numbers
20
+         * or an object with countries as keys and an array of numbers
21
+         * as values.
22
+         */
23
+        numbers: PropTypes.oneOfType([
24
+            PropTypes.array,
25
+            PropTypes.object
26
+        ]),
27
+
28
+        /**
29
+         * Invoked to obtain translated strings.
30
+         */
31
+        t: PropTypes.func
32
+    };
33
+
34
+    /**
35
+     * Implements React's {@link Component#render()}.
36
+     *
37
+     * @inheritdoc
38
+     * @returns {ReactElement}
39
+     */
40
+    render() {
41
+        const { numbers, t } = this.props;
42
+        const showWithoutCountries = Array.isArray(numbers);
43
+
44
+        return (
45
+            <table className = 'dial-in-numbers-list'>
46
+                <thead>
47
+                    <tr>
48
+                        { showWithoutCountries
49
+                            ? null
50
+                            : <th>{ t('info.country') }</th> }
51
+                        <th>{ t('info.numbers') }</th>
52
+                    </tr>
53
+                </thead>
54
+                <tbody>
55
+                    { showWithoutCountries
56
+                        ? numbers.map(this._renderNumberRow)
57
+                        : this._renderWithCountries() }
58
+                </tbody>
59
+            </table>);
60
+    }
61
+
62
+    /**
63
+     * Renders rows of countries and associated phone numbers.
64
+     *
65
+     * @private
66
+     * @returns {ReactElement[]}
67
+     */
68
+    _renderWithCountries() {
69
+        const rows = [];
70
+
71
+        for (const [ country, numbers ] of Object.entries(this.props.numbers)) {
72
+            const formattedNumbers = numbers.map(this._renderNumberDiv);
73
+
74
+            rows.push(
75
+                <tr key = { country }>
76
+                    <td>{ country }</td>
77
+                    <td className = 'dial-in-numbers'>{ formattedNumbers }</td>
78
+                </tr>
79
+            );
80
+        }
81
+
82
+        return rows;
83
+    }
84
+
85
+    /**
86
+     * Renders a table row for a phone number.
87
+     *
88
+     * @param {string} number - The phone number to display.
89
+     * @private
90
+     * @returns {ReactElement[]}
91
+     */
92
+    _renderNumberRow(number) {
93
+        return (
94
+            <tr key = { number }>
95
+                <td className = 'dial-in-number'>
96
+                    { number }
97
+                </td>
98
+            </tr>
99
+        );
100
+    }
101
+
102
+    /**
103
+     * Renders a div container for a phone number.
104
+     *
105
+     * @param {string} number - The phone number to display.
106
+     * @private
107
+     * @returns {ReactElement[]}
108
+     */
109
+    _renderNumberDiv(number) {
110
+        return (
111
+            <div
112
+                className = 'dial-in-number'
113
+                key = { number }>
114
+                { number }
115
+            </div>
116
+        );
117
+    }
118
+}
119
+
120
+export default translate(NumbersList);

+ 1
- 0
react/features/invite/components/dial-in-info-page/index.js 파일 보기

@@ -0,0 +1 @@
1
+export { default as DialInInfoApp } from './DialInInfoApp';

+ 0
- 0
react/features/invite/components/info-dialog/DialInNumber.native.js 파일 보기


+ 60
- 0
react/features/invite/components/info-dialog/DialInNumber.web.js 파일 보기

@@ -0,0 +1,60 @@
1
+import React, { Component } from 'react';
2
+import PropTypes from 'prop-types';
3
+
4
+import { translate } from '../../../base/i18n';
5
+
6
+/**
7
+ * React {@code Component} responsible for displaying a telephone number and
8
+ * conference ID for dialing into a conference.
9
+ *
10
+ * @extends Component
11
+ */
12
+class DialInNumber extends Component {
13
+    /**
14
+     * {@code DialInNumber} component's property types.
15
+     *
16
+     * @static
17
+     */
18
+    static propTypes = {
19
+        /**
20
+         * The numberic identifier for the current conference, used after
21
+         * dialing a the number to join the conference.
22
+         */
23
+        conferenceID: PropTypes.number,
24
+
25
+        /**
26
+         * The phone number to dial to begin the process of dialing into a
27
+         * conference.
28
+         */
29
+        phoneNumber: PropTypes.string,
30
+
31
+        /**
32
+         * Invoked to obtain translated strings.
33
+         */
34
+        t: PropTypes.func
35
+    };
36
+
37
+    /**
38
+     * Implements React's {@link Component#render()}.
39
+     *
40
+     * @inheritdoc
41
+     * @returns {ReactElement}
42
+     */
43
+    render() {
44
+        const { conferenceID, phoneNumber } = this.props;
45
+
46
+        return (
47
+            <div className = 'dial-in-number'>
48
+                <span className = 'phone-number'>
49
+                    { this.props.t('info.dialInNumber', { phoneNumber }) }
50
+                </span>
51
+                <span className = 'conference-id'>
52
+                    { this.props.t(
53
+                        'info.dialInConferenceID', { conferenceID }) }
54
+                </span>
55
+            </div>
56
+        );
57
+    }
58
+}
59
+
60
+export default translate(DialInNumber);

+ 0
- 0
react/features/invite/components/info-dialog/InfoDialog.native.js 파일 보기


+ 503
- 0
react/features/invite/components/info-dialog/InfoDialog.web.js 파일 보기

@@ -0,0 +1,503 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { setPassword } from '../../../base/conference';
6
+import { getInviteURL } from '../../../base/connection';
7
+import { translate } from '../../../base/i18n';
8
+import {
9
+    PARTICIPANT_ROLE,
10
+    getLocalParticipant
11
+} from '../../../base/participants';
12
+
13
+import { updateDialInNumbers } from '../../actions';
14
+
15
+import DialInNumber from './DialInNumber';
16
+import PasswordForm from './PasswordForm';
17
+
18
+const logger = require('jitsi-meet-logger').getLogger(__filename);
19
+
20
+/**
21
+ * A React Component with the contents for a dialog that shows information about
22
+ * the current conference.
23
+ *
24
+ * @extends Component
25
+ */
26
+class InfoDialog extends Component {
27
+    /**
28
+     * {@code InfoDialog} component's property types.
29
+     *
30
+     * @static
31
+     */
32
+    static propTypes = {
33
+        /**
34
+         * Whether or not the current user can modify the current password.
35
+         */
36
+        _canEditPassword: PropTypes.bool,
37
+
38
+        /**
39
+         * The JitsiConference for which to display a lock state and change the
40
+         * password.
41
+         *
42
+         * @type {JitsiConference}
43
+         */
44
+        _conference: PropTypes.object,
45
+
46
+        /**
47
+         * The name of the current conference. Used as part of inviting users.
48
+         */
49
+        _conferenceName: PropTypes.string,
50
+
51
+        /**
52
+         * The redux state representing the dial-in numbers feature.
53
+         */
54
+        _dialIn: PropTypes.object,
55
+
56
+        /**
57
+         * The current url of the conference to be copied onto the clipboard.
58
+         */
59
+        _inviteURL: PropTypes.string,
60
+
61
+        /**
62
+         * The value for how the conference is locked (or undefined if not
63
+         * locked) as defined by room-lock constants.
64
+         */
65
+        _locked: PropTypes.string,
66
+
67
+        /**
68
+         * The current known password for the JitsiConference.
69
+         */
70
+        _password: PropTypes.string,
71
+
72
+        /**
73
+         * Invoked to open a dialog for adding participants to the conference.
74
+         */
75
+        dispatch: PropTypes.func,
76
+
77
+        /**
78
+         * Callback invoked when the dialog should be closed.
79
+         */
80
+        onClose: PropTypes.func,
81
+
82
+        /**
83
+         * Callback invoked when a mouse-related event has been detected.
84
+         */
85
+        onMouseOver: PropTypes.func,
86
+
87
+        /**
88
+         * Invoked to obtain translated strings.
89
+         */
90
+        t: PropTypes.func
91
+    };
92
+
93
+    /**
94
+     * {@code InfoDialog} component's local state.
95
+     *
96
+     * @type {Object}
97
+     * @property {boolean} passwordEditEnabled - Whether or not to show the
98
+     * {@code PasswordForm} in its editing state.
99
+     * @property {string} phoneNumber - The number to display for dialing into
100
+     * the conference.
101
+     */
102
+    state = {
103
+        passwordEditEnabled: false,
104
+        phoneNumber: ''
105
+    };
106
+
107
+    /**
108
+     * Initializes new {@code InfoDialog} instance.
109
+     *
110
+     * @param {Object} props - The read-only properties with which the new
111
+     * instance is to be initialized.
112
+     */
113
+    constructor(props) {
114
+        super(props);
115
+
116
+        const { defaultCountry, numbers } = props._dialIn;
117
+
118
+        if (numbers) {
119
+            this.state.phoneNumber
120
+                = this._getDefaultPhoneNumber(numbers, defaultCountry);
121
+        }
122
+
123
+        /**
124
+         * The internal reference to the DOM/HTML element backing the React
125
+         * {@code Component} text area. It is necessary for the implementation
126
+         * of copying to the clipboard.
127
+         *
128
+         * @private
129
+         * @type {HTMLTextAreaElement}
130
+         */
131
+        this._copyElement = null;
132
+
133
+        // Bind event handlers so they are only bound once for every instance.
134
+        this._onCopyInviteURL = this._onCopyInviteURL.bind(this);
135
+        this._onPasswordRemove = this._onPasswordRemove.bind(this);
136
+        this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
137
+        this._onTogglePasswordEditState
138
+            = this._onTogglePasswordEditState.bind(this);
139
+        this._setCopyElement = this._setCopyElement.bind(this);
140
+    }
141
+
142
+    /**
143
+     * Implements {@link Component#componentDidMount()}. Invoked immediately
144
+     * after this component is mounted. Requests dial-in numbers if not
145
+     * already known.
146
+     *
147
+     * @inheritdoc
148
+     * @returns {void}
149
+     */
150
+    componentDidMount() {
151
+        if (!this.state.phoneNumber) {
152
+            this.props.dispatch(updateDialInNumbers());
153
+        }
154
+    }
155
+
156
+    /**
157
+     * Implements React's {@link Component#componentWillReceiveProps()}. Invoked
158
+     * before this mounted component receives new props.
159
+     *
160
+     * @inheritdoc
161
+     * @param {Props} nextProps - New props component will receive.
162
+     */
163
+    componentWillReceiveProps(nextProps) {
164
+        if (!this.props._password && nextProps._password) {
165
+            this.setState({ passwordEditEnabled: false });
166
+        }
167
+
168
+        if (!this.state.phoneNumber && nextProps._dialIn.numbers) {
169
+            const { defaultCountry, numbers } = nextProps._dialIn;
170
+
171
+            this.setState({
172
+                phoneNumber:
173
+                    this._getDefaultPhoneNumber(numbers, defaultCountry)
174
+            });
175
+        }
176
+    }
177
+
178
+    /**
179
+     * Implements React's {@link Component#render()}.
180
+     *
181
+     * @inheritdoc
182
+     * @returns {ReactElement}
183
+     */
184
+    render() {
185
+        const { onMouseOver, t } = this.props;
186
+
187
+        return (
188
+            <div
189
+                className = 'info-dialog'
190
+                onMouseOver = { onMouseOver } >
191
+                <div className = 'info-dialog-column'>
192
+                    <h4 className = 'info-dialog-icon'>
193
+                        <i className = 'icon-info' />
194
+                    </h4>
195
+                </div>
196
+                <div className = 'info-dialog-column'>
197
+                    <div className = 'info-dialog-title'>
198
+                        { t('info.title') }
199
+                    </div>
200
+                    <div className = 'info-dialog-conference-url'>
201
+                        { t('info.conferenceURL',
202
+                            { url: this._getURLToDisplay() }) }
203
+                        <textarea
204
+                            className = 'info-dialog-copy-element'
205
+                            readOnly = { true }
206
+                            ref = { this._setCopyElement }
207
+                            tabIndex = '-1'
208
+                            value = { this._getTextToCopy() } />
209
+                    </div>
210
+                    <div className = 'info-dialog-dial-in'>
211
+                        { this._renderDialInDisplay() }
212
+                    </div>
213
+                    <div className = 'info-dialog-password'>
214
+                        <PasswordForm
215
+                            editEnabled = { this.state.passwordEditEnabled }
216
+                            locked = { this.props._locked }
217
+                            onSubmit = { this._onPasswordSubmit }
218
+                            password = { this.props._password } />
219
+                    </div>
220
+                    <div className = 'info-dialog-action-links'>
221
+                        <div className = 'info-dialog-action-link'>
222
+                            <a
223
+                                className = 'info-copy'
224
+                                onClick = { this._onCopyInviteURL }>
225
+                                { t('dialog.copy') }
226
+                            </a>
227
+                        </div>
228
+                        { this._renderPasswordAction() }
229
+                    </div>
230
+                </div>
231
+            </div>
232
+        );
233
+    }
234
+
235
+    /**
236
+     * Sets the internal state of which dial-in number to display.
237
+     *
238
+     * @param {Array<string>|Object} dialInNumbers - The array or object of
239
+     * numbers to choose a number from.
240
+     * @param {string} defaultCountry - The country code for the country
241
+     * whose phone number should display.
242
+     * @private
243
+     * @returns {string|null}
244
+     */
245
+    _getDefaultPhoneNumber(dialInNumbers, defaultCountry = 'US') {
246
+        if (Array.isArray(dialInNumbers)) {
247
+            // Dumbly return the first number if an array.
248
+            return dialInNumbers[0];
249
+        } else if (Object.keys(dialInNumbers).length > 0) {
250
+            const defaultNumbers = dialInNumbers[defaultCountry];
251
+
252
+            if (defaultNumbers) {
253
+                return defaultNumbers[0];
254
+            }
255
+
256
+            const firstRegion = Object.keys(dialInNumbers)[0];
257
+
258
+            return firstRegion && firstRegion[0];
259
+        }
260
+
261
+        return null;
262
+    }
263
+
264
+    /**
265
+     * Creates a message describing how to dial in to the conference.
266
+     *
267
+     * @private
268
+     * @returns {string}
269
+     */
270
+    _getTextToCopy() {
271
+        const { _conferenceName, t } = this.props;
272
+
273
+        let invite = t('info.inviteURL', {
274
+            url: this.props._inviteURL
275
+        });
276
+
277
+        if (this._shouldDisplayDialIn()) {
278
+            const dial = t('info.invitePhone', {
279
+                number: this.state.phoneNumber,
280
+                conferenceID: this.props._dialIn.conferenceID
281
+            });
282
+            const moreNumbers = t('info.invitePhoneAlternatives', {
283
+                url: `${window.location.origin}/static/dialInInfo.html?room=${
284
+                    encodeURIComponent(_conferenceName)}`
285
+            });
286
+
287
+            invite = `${invite}\n${dial}\n${moreNumbers}`;
288
+        }
289
+
290
+        return invite;
291
+    }
292
+
293
+    /**
294
+     * Modifies the inviteURL for display in the modal.
295
+     *
296
+     * @private
297
+     * @returns {string}
298
+     */
299
+    _getURLToDisplay() {
300
+        return this.props._inviteURL.replace(/^https?:\/\//i, '');
301
+    }
302
+
303
+    /**
304
+     * Callback invoked to copy the contents of {@code this._copyElement} to the
305
+     * clipboard.
306
+     *
307
+     * @private
308
+     * @returns {void}
309
+     */
310
+    _onCopyInviteURL() {
311
+        try {
312
+            this._copyElement.select();
313
+            document.execCommand('copy');
314
+            this._copyElement.blur();
315
+        } catch (err) {
316
+            logger.error('error when copying the text', err);
317
+        }
318
+    }
319
+
320
+    /**
321
+     * Callback invoked to unlock the current JitsiConference.
322
+     *
323
+     * @private
324
+     * @returns {void}
325
+     */
326
+    _onPasswordRemove() {
327
+        this._onPasswordSubmit('');
328
+    }
329
+
330
+    /**
331
+     * Callback invoked to set a password on the current JitsiConference.
332
+     *
333
+     * @param {string} enteredPassword - The new password to be used to lock the
334
+     * current JitsiConference.
335
+     * @private
336
+     * @returns {void}
337
+     */
338
+    _onPasswordSubmit(enteredPassword) {
339
+        const { _conference } = this.props;
340
+
341
+        this.props.dispatch(setPassword(
342
+            _conference,
343
+            _conference.lock,
344
+            enteredPassword
345
+        ));
346
+    }
347
+
348
+    /**
349
+     * Toggles whether or not the password should currently be shown as being
350
+     * edited locally.
351
+     *
352
+     * @private
353
+     * @returns {void}
354
+     */
355
+    _onTogglePasswordEditState() {
356
+        this.setState({
357
+            passwordEditEnabled: !this.state.passwordEditEnabled
358
+        });
359
+    }
360
+
361
+    /**
362
+     * Returns a ReactElement for showing how to dial into the conference, if
363
+     * dialing in is available.
364
+     *
365
+     * @private
366
+     * @returns {null|ReactElement}
367
+     */
368
+    _renderDialInDisplay() {
369
+        if (!this._shouldDisplayDialIn()) {
370
+            return null;
371
+        }
372
+
373
+        return (
374
+            <div>
375
+                <DialInNumber
376
+                    conferenceID = { this.props._dialIn.conferenceID }
377
+                    phoneNumber = { this.state.phoneNumber } />
378
+                <a
379
+                    className = 'more-numbers'
380
+                    href = { `static/dialInInfo.html?room=${
381
+                        encodeURIComponent(this.props._conferenceName)}` }
382
+                    rel = 'noopener noreferrer'
383
+                    target = '_blank'>
384
+                    { this.props.t('info.moreNumbers') }
385
+                </a>
386
+            </div>
387
+        );
388
+    }
389
+
390
+    /**
391
+     * Returns a ReactElement for interacting with the password field.
392
+     *
393
+     * @private
394
+     * @returns {null|ReactElement}
395
+     */
396
+    _renderPasswordAction() {
397
+        const { t } = this.props;
398
+        let className, onClick, textKey;
399
+
400
+
401
+        if (!this.props._canEditPassword) {
402
+            // intentionally left blank to prevent rendering anything
403
+        } else if (this.state.passwordEditEnabled) {
404
+            className = 'cancel-password';
405
+            onClick = this._onTogglePasswordEditState;
406
+            textKey = 'info.cancelPassword';
407
+        } else if (this.props._locked) {
408
+            className = 'remove-password';
409
+            onClick = this._onPasswordRemove;
410
+            textKey = 'dialog.removePassword';
411
+        } else {
412
+            className = 'add-password';
413
+            onClick = this._onTogglePasswordEditState;
414
+            textKey = 'invite.addPassword';
415
+        }
416
+
417
+        return className && onClick && textKey
418
+            ? <div className = 'info-dialog-action-link'>
419
+                <a
420
+                    className = { className }
421
+                    onClick = { onClick }>
422
+                    { t(textKey) }
423
+                </a>
424
+            </div>
425
+            : null;
426
+    }
427
+
428
+    /**
429
+     * Returns whether or not dial-in related UI should be displayed.
430
+     *
431
+     * @private
432
+     * @returns {boolean}
433
+     */
434
+    _shouldDisplayDialIn() {
435
+        const { conferenceID, numbers, numbersEnabled } = this.props._dialIn;
436
+        const { phoneNumber } = this.state;
437
+
438
+        return Boolean(
439
+            conferenceID
440
+            && numbers
441
+            && numbersEnabled
442
+            && phoneNumber);
443
+    }
444
+
445
+    /**
446
+     * Sets the internal reference to the DOM/HTML element backing the React
447
+     * {@code Component} input.
448
+     *
449
+     * @param {HTMLInputElement} element - The DOM/HTML element for this
450
+     * {@code Component}'s input.
451
+     * @private
452
+     * @returns {void}
453
+     */
454
+    _setCopyElement(element) {
455
+        this._copyElement = element;
456
+    }
457
+}
458
+
459
+/**
460
+ * Maps (parts of) the Redux state to the associated props for the
461
+ * {@code InfoDialog} component.
462
+ *
463
+ * @param {Object} state - The Redux state.
464
+ * @private
465
+ * @returns {{
466
+ *     _canEditPassword: boolean,
467
+ *     _conference: Object,
468
+ *     _conferenceName: string,
469
+ *     _dialIn: Object,
470
+ *     _inviteURL: string,
471
+ *     _locked: string,
472
+ *     _password: string
473
+ * }}
474
+ */
475
+function _mapStateToProps(state) {
476
+    const {
477
+        conference,
478
+        locked,
479
+        password,
480
+        room
481
+    } = state['features/base/conference'];
482
+    const isModerator
483
+        = getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR;
484
+    let canEditPassword;
485
+
486
+    if (state['features/base/config'].enableUserRolesBasedOnToken) {
487
+        canEditPassword = isModerator && !state['features/base/jwt'].isGuest;
488
+    } else {
489
+        canEditPassword = isModerator;
490
+    }
491
+
492
+    return {
493
+        _canEditPassword: canEditPassword,
494
+        _conference: conference,
495
+        _conferenceName: room,
496
+        _dialIn: state['features/invite'],
497
+        _inviteURL: getInviteURL(state),
498
+        _locked: locked,
499
+        _password: password
500
+    };
501
+}
502
+
503
+export default translate(connect(_mapStateToProps)(InfoDialog));

+ 0
- 0
react/features/invite/components/info-dialog/PasswordForm.native.js 파일 보기


+ 175
- 0
react/features/invite/components/info-dialog/PasswordForm.web.js 파일 보기

@@ -0,0 +1,175 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+
4
+import { translate } from '../../../base/i18n';
5
+import { LOCKED_LOCALLY } from '../../../room-lock';
6
+
7
+/**
8
+ * React {@code Component} for displaying and editing the conference password.
9
+ *
10
+ * @extends Component
11
+ */
12
+class PasswordForm extends Component {
13
+    /**
14
+     * {@code PasswordForm} component's property types.
15
+     *
16
+     * @static
17
+     */
18
+    static propTypes = {
19
+        /**
20
+         * Whether or not to show the password editing field.
21
+         */
22
+        editEnabled: PropTypes.bool,
23
+
24
+        /**
25
+         * The value for how the conference is locked (or undefined if not
26
+         * locked) as defined by room-lock constants.
27
+         */
28
+        locked: PropTypes.string,
29
+
30
+        /**
31
+         * Callback to invoke when the local participant is submitting a
32
+         * password set request.
33
+         */
34
+        onSubmit: PropTypes.func,
35
+
36
+        /**
37
+         * The current known password for the JitsiConference.
38
+         */
39
+        password: PropTypes.string,
40
+
41
+        /**
42
+         * Invoked to obtain translated strings.
43
+         */
44
+        t: PropTypes.func
45
+    };
46
+
47
+    /**
48
+     * {@code PasswordForm} component's local state.
49
+     *
50
+     * @type {Object}
51
+     * @property {string} enteredPassword - The value of the password being
52
+     * entered by the local participant.
53
+     */
54
+    state = {
55
+        enteredPassword: ''
56
+    };
57
+
58
+    /**
59
+     * Initializes a new {@code PasswordForm} instance.
60
+     *
61
+     * @param {Props} props - The React {@code Component} props to initialize
62
+     * the new {@code PasswordForm} instance with.
63
+     */
64
+    constructor(props) {
65
+        super(props);
66
+
67
+        // Bind event handlers so they are only bound once per instance.
68
+        this._onEnteredPasswordChange
69
+            = this._onEnteredPasswordChange.bind(this);
70
+        this._onPasswordSubmit = this._onPasswordSubmit.bind(this);
71
+    }
72
+
73
+    /**
74
+     * Implements React's {@link Component#componentWillReceiveProps()}. Invoked
75
+     * before this mounted component receives new props.
76
+     *
77
+     * @inheritdoc
78
+     * @param {Props} nextProps - New props component will receive.
79
+     */
80
+    componentWillReceiveProps(nextProps) {
81
+        if (this.props.editEnabled && !nextProps.editEnabled) {
82
+            this.setState({ enteredPassword: '' });
83
+        }
84
+    }
85
+
86
+    /**
87
+     * Implements React's {@link Component#render()}.
88
+     *
89
+     * @inheritdoc
90
+     * @returns {ReactElement}
91
+     */
92
+    render() {
93
+        const { t } = this.props;
94
+
95
+        return (
96
+            <div className = 'info-password'>
97
+                <div>{ t('info.password') }</div>
98
+                <div className = 'info-password-field'>
99
+                    { this._renderPasswordField() }
100
+                </div>
101
+            </div>
102
+        );
103
+    }
104
+
105
+    /**
106
+     * Returns a ReactElement for showing the current state of the password or
107
+     * for editing the current password.
108
+     *
109
+     * @private
110
+     * @returns {ReactElement}
111
+     */
112
+    _renderPasswordField() {
113
+        if (this.props.editEnabled) {
114
+            return (
115
+                <form
116
+                    className = 'info-password-form'
117
+                    onSubmit = { this._onPasswordSubmit }>
118
+                    <input
119
+                        autoFocus = { true }
120
+                        className = 'info-password-input'
121
+                        onChange = { this._onEnteredPasswordChange }
122
+                        spellCheck = { 'false' }
123
+                        type = 'text'
124
+                        value = { this.state.enteredPassword } />
125
+                </form>
126
+            );
127
+        } else if (this.props.locked === LOCKED_LOCALLY) {
128
+            return (
129
+                <div className = 'info-password-local'>
130
+                    { this.props.password }
131
+                </div>
132
+            );
133
+        } else if (this.props.locked) {
134
+            return (
135
+                <div className = 'info-password-remote'>
136
+                    { this.props.t('passwordSetRemotely') }
137
+                </div>
138
+            );
139
+        }
140
+
141
+        return (
142
+            <div className = 'info-password-none'>
143
+                { this.props.t('info.noPassword') }
144
+            </div>
145
+        );
146
+    }
147
+
148
+    /**
149
+     * Updates the internal state of entered password.
150
+     *
151
+     * @param {Object} event - DOM Event for value change.
152
+     * @private
153
+     * @returns {void}
154
+     */
155
+    _onEnteredPasswordChange(event) {
156
+        this.setState({ enteredPassword: event.target.value });
157
+    }
158
+
159
+    /**
160
+     * Invokes the passed in onSubmit callback to notify the parent that a
161
+     * password submission has been attempted.
162
+     *
163
+     * @param {Object} event - DOM Event for form submission.
164
+     * @private
165
+     * @returns {void}
166
+     */
167
+    _onPasswordSubmit(event) {
168
+        event.preventDefault();
169
+
170
+        this.props.onSubmit(this.state.enteredPassword);
171
+    }
172
+}
173
+
174
+
175
+export default translate(PasswordForm);

+ 1
- 0
react/features/invite/components/info-dialog/index.js 파일 보기

@@ -0,0 +1 @@
1
+export { default as InfoDialog } from './InfoDialog';

+ 7
- 1
react/features/invite/reducer.js 파일 보기

@@ -26,10 +26,16 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
26 26
         };
27 27
 
28 28
     case UPDATE_DIAL_IN_NUMBERS_SUCCESS: {
29
-        const { numbers, numbersEnabled } = action.dialInNumbers;
29
+        const {
30
+            defaultCountry,
31
+            numbers,
32
+            numbersEnabled
33
+        } = action.dialInNumbers;
30 34
 
31 35
         return {
36
+            ...state,
32 37
             conferenceID: action.conferenceID,
38
+            defaultCountry,
33 39
             numbers,
34 40
             numbersEnabled
35 41
         };

+ 17
- 0
static/dialInInfo.html 파일 보기

@@ -0,0 +1,17 @@
1
+<html>
2
+  <head>
3
+    <meta charset="utf-8">
4
+    <meta http-equiv="content-type" content="text/html;charset=utf-8">
5
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+    <base href="../" />
7
+    <!--#include virtual="/title.html" -->
8
+
9
+    <link rel="stylesheet" href="css/all.css">
10
+  </head>
11
+  <body>
12
+    <div id="react"></div>
13
+    <script><!--#include virtual="/config.js" --></script>
14
+    <script><!--#include virtual="/interface_config.js" --></script>
15
+    <script src="libs/dial_in_info_bundle.min.js"></script>
16
+  </body>
17
+</html>

+ 12
- 0
webpack.config.js 파일 보기

@@ -150,6 +150,18 @@ module.exports = [
150 150
             'alwaysontop':
151 151
                 './react/features/always-on-top/index.js',
152 152
 
153
+            'dial_in_info_bundle': [
154
+
155
+                // babel-polyfill and fetch polyfill are required for IE11.
156
+                'babel-polyfill',
157
+                'whatwg-fetch',
158
+
159
+                // atlaskit does not support React 16 prop-types
160
+                './react/features/base/react/prop-types-polyfill.js',
161
+
162
+                './react/features/invite/components/dial-in-info-page'
163
+            ],
164
+
153 165
             'do_external_connect':
154 166
                 './connection_optimization/do_external_connect.js'
155 167
         }

Loading…
취소
저장