瀏覽代碼

ref(settings): convert panel to react

The goal is to reduce usage on atlassian/aui. New components
have been created to display the settings panel. Language
selection will reach into i18n for state whereas moderator
options will keep state in redux.
j8
Leonard Kim 7 年之前
父節點
當前提交
c9b54845d9

+ 3
- 13
conference.js 查看文件

@@ -29,6 +29,7 @@ import {
29 29
     dataChannelOpened,
30 30
     EMAIL_COMMAND,
31 31
     lockStateChanged,
32
+    onStartMutedPolicyChanged,
32 33
     p2pStatusChanged,
33 34
     sendLocalParticipant
34 35
 } from './react/features/base/conference';
@@ -2078,18 +2079,11 @@ export default {
2078 2079
         APP.UI.addListener(UIEvents.NICKNAME_CHANGED,
2079 2080
             this.changeLocalDisplayName.bind(this));
2080 2081
 
2081
-        APP.UI.addListener(UIEvents.START_MUTED_CHANGED,
2082
-            (startAudioMuted, startVideoMuted) => {
2083
-                room.setStartMutedPolicy({
2084
-                    audio: startAudioMuted,
2085
-                    video: startVideoMuted
2086
-                });
2087
-            }
2088
-        );
2089 2082
         room.on(
2090 2083
             JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
2091 2084
             ({ audio, video }) => {
2092
-                APP.UI.onStartMutedChanged(audio, video);
2085
+                APP.store.dispatch(
2086
+                    onStartMutedPolicyChanged(audio, video));
2093 2087
             }
2094 2088
         );
2095 2089
         room.on(JitsiConferenceEvents.STARTED_MUTED, () => {
@@ -2373,10 +2367,6 @@ export default {
2373 2367
 
2374 2368
         APP.UI.initConference();
2375 2369
 
2376
-        APP.UI.addListener(
2377
-            UIEvents.LANG_CHANGED,
2378
-            language => APP.translation.setLanguage(language));
2379
-
2380 2370
         APP.keyboardshortcut.init();
2381 2371
 
2382 2372
         if (config.requireDisplayName

+ 24
- 37
css/_side_toolbar_container.scss 查看文件

@@ -22,8 +22,10 @@
22 22
     /**
23 23
      * Form elements and blocks.
24 24
      */
25
-    input, select, a,
26
-    .sideToolbarBlock, .form-control, .button-control {
25
+    input,
26
+    a,
27
+    .sideToolbarBlock,
28
+    .form-control {
27 29
         display: block;
28 30
         margin-top: 15px;
29 31
         margin-left: 10%;
@@ -34,19 +36,11 @@
34 36
      * Specify styling of elements inside a block.
35 37
      */
36 38
     .sideToolbarBlock {
37
-        input, button, a, select {
39
+        input, a {
38 40
             margin-left: 0;
39 41
             margin-top: 5px;
40 42
             width: 100%;
41 43
         }
42
-        input[type='checkbox'] {
43
-            display: inline;
44
-            width: auto !important;
45
-            > label {
46
-                margin-top: 5px;
47
-                width: 80%;
48
-            }
49
-        }
50 44
     }
51 45
 
52 46
     /**
@@ -80,42 +74,35 @@
80 74
             font-size: $toolbarTitleFontSize;
81 75
         }
82 76
 
83
-        /**
84
-         * Subtitle specific properties.
85
-         */
86
-        div.subTitle {
87
-            color: $defaultSideBarFontColor !important;
88
-            font-size: 11px;
89
-            font-weight: 500;
90
-            margin-left: 10%;
91
-            text-align: left;
92
-        }
93
-
94 77
         /**
95 78
          * First element after a title.
96 79
          */
97 80
         .first {
98 81
             margin-top: 0 !important;
99 82
         }
83
+    }
100 84
 
101
-        /**
102
-         * Buttons in the side toolbar container.
103
-         */
104
-        .button-control {
105
-            margin: 9px 0;
106
-            width: 100%;
85
+    .settings-menu {
86
+        display: flex;
87
+        flex-direction: column;
88
+        padding-left: 10%;
89
+        padding-right: 10%;
90
+
91
+        .moderator-checkbox {
92
+            display: inline-block;
93
+            margin: 0 5px 0;
94
+            width: auto;
107 95
         }
108
-    }
109
-}
110 96
 
111
-#device_settings {
112
-    width : auto !important;
113
-    text-align: center;
114
-}
97
+        .moderator-option {
98
+            margin-top: 15px;
99
+        }
115 100
 
116
-#deviceOptionsWrapper {
117
-    button {
118
-        float: none;
101
+        .subTitle {
102
+            color: $defaultSideBarFontColor;
103
+            font-size: 11px;
104
+            font-weight: 500;
105
+        }
119 106
     }
120 107
 }
121 108
 

+ 0
- 7
modules/UI/UI.js 查看文件

@@ -17,7 +17,6 @@ import Recording from './recording/Recording';
17 17
 
18 18
 import VideoLayout from './videolayout/VideoLayout';
19 19
 import Filmstrip from './videolayout/Filmstrip';
20
-import SettingsMenu from './side_pannels/settings/SettingsMenu';
21 20
 import Profile from './side_pannels/profile/Profile';
22 21
 
23 22
 import {
@@ -541,8 +540,6 @@ UI.updateLocalRole = isModerator => {
541 540
     APP.store.dispatch(showSharedVideoButton());
542 541
 
543 542
     Recording.showRecordingButton(isModerator);
544
-    SettingsMenu.showStartMutedOptions(isModerator);
545
-    SettingsMenu.showFollowMeOptions(isModerator);
546 543
 
547 544
     if (isModerator) {
548 545
         if (!interfaceConfig.DISABLE_FOCUS_INDICATOR) {
@@ -1017,10 +1014,6 @@ UI.updateAuthInfo = function(isAuthEnabled, login) {
1017 1014
     }
1018 1015
 };
1019 1016
 
1020
-UI.onStartMutedChanged = function(startAudioMuted, startVideoMuted) {
1021
-    SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
1022
-};
1023
-
1024 1017
 /**
1025 1018
  * Notifies interested listeners that the raise hand property has changed.
1026 1019
  *

+ 30
- 223
modules/UI/side_pannels/settings/SettingsMenu.js 查看文件

@@ -1,235 +1,42 @@
1
-/* global $, APP, AJS, interfaceConfig */
2
-import { LANGUAGES } from '../../../../react/features/base/i18n';
3
-import { openDeviceSelectionDialog }
4
-    from '../../../../react/features/device-selection';
1
+/* global $, APP, interfaceConfig */
5 2
 
6
-import UIUtil from '../../util/UIUtil';
7
-import UIEvents from '../../../../service/UI/UIEvents';
8
-
9
-const sidePanelsContainerId = 'sideToolbarContainer';
10
-const deviceSelectionButtonClasses
11
-    = 'button-control button-control_primary button-control_full-width';
12
-const htmlStr = `
13
-    <div id="settings_container" class="sideToolbarContainer__inner">
14
-        <div class="title" data-i18n="settings.title"></div>
15
-        <form class="aui">
16
-            <div id="languagesSelectWrapper"
17
-                class="sideToolbarBlock first hide">
18
-                <select id="languagesSelect"></select>
19
-            </div>
20
-            <div id="deviceOptionsWrapper" class="hide">
21
-                <div id="deviceOptionsTitle" class="subTitle hide"
22
-                    data-i18n="settings.audioVideo"></div>
23
-                <div class="sideToolbarBlock first">
24
-                    <button
25
-                        class="${deviceSelectionButtonClasses}"
26
-                        data-i18n="deviceSelection.deviceSettings"
27
-                        id="deviceSelection"
28
-                        type="button"></button>
29
-                </div>
30
-            </div>
31
-            <div id="moderatorOptionsWrapper" class="hide">
32
-                <div id="moderatorOptionsTitle" class="subTitle hide"
33
-                    data-i18n="settings.moderator"></div>
34
-                <div id="startMutedOptions" class="hide">
35
-                    <div class="sideToolbarBlock first">
36
-                        <input type="checkbox" id="startAudioMuted">
37
-                        <label class="startMutedLabel" for="startAudioMuted"
38
-                            data-i18n="settings.startAudioMuted"></label>
39
-                    </div>
40
-                    <div class="sideToolbarBlock">
41
-                        <input type="checkbox" id="startVideoMuted">
42
-                        <label class="startMutedLabel" for="startVideoMuted"
43
-                            data-i18n="settings.startVideoMuted"></label>
44
-                    </div>
45
-                </div>
46
-                <div id="followMeOptions" class="hide">
47
-                    <div class="sideToolbarBlock">
48
-                        <input type="checkbox" id="followMeCheckBox">
49
-                        <label class="followMeLabel" for="followMeCheckBox"
50
-                            data-i18n="settings.followMe"></label>
51
-                    </div>
52
-                </div>
53
-            </div>
54
-        </form>
55
-    </div>`;
56
-
57
-/**
58
- *
59
- */
60
-function initHTML() {
61
-    $(`#${sidePanelsContainerId}`)
62
-        .append(htmlStr);
63
-
64
-    // make sure we translate the panel, as adding it can be after i18n
65
-    // library had initialized and translated already present html
66
-    APP.translation.translateElement($(`#${sidePanelsContainerId}`));
67
-}
68
-
69
-/**
70
- * Generate html select options for available languages.
71
- *
72
- * @param {string[]} items available languages
73
- * @param {string} [currentLang] current language
74
- * @returns {string}
75
- */
76
-function generateLanguagesOptions(items, currentLang) {
77
-    return items.map(lang => {
78
-        const attrs = {
79
-            value: lang,
80
-            'data-i18n': `languages:${lang}`
81
-        };
82
-
83
-        if (lang === currentLang) {
84
-            attrs.selected = 'selected';
85
-        }
86
-
87
-        const attrsStr = UIUtil.attrsToString(attrs);
3
+/* eslint-disable no-unused-vars */
4
+import React from 'react';
5
+import ReactDOM from 'react-dom';
6
+import { I18nextProvider } from 'react-i18next';
7
+import { Provider } from 'react-redux';
88 8
 
9
+import { i18next } from '../../../../react/features/base/i18n';
10
+import { SettingsMenu } from '../../../../react/features/settings-menu';
11
+/* eslint-enable no-unused-vars */
89 12
 
90
-        return `<option ${attrsStr}></option>`;
91
-    }).join('');
92
-}
93
-
94
-/**
95
- * Replace html select element to select2 custom dropdown
96
- *
97
- * @param {jQueryElement} $el native select element
98
- * @param {function} onSelectedCb fired if item is selected
99
- */
100
-function initSelect2($el, onSelectedCb) {
101
-    $el.auiSelect2({
102
-        minimumResultsForSearch: Infinity
103
-    });
104
-    if (typeof onSelectedCb === 'function') {
105
-        $el.change(onSelectedCb);
106
-    }
107
-}
13
+import UIUtil from '../../util/UIUtil';
108 14
 
109 15
 export default {
110
-    init(emitter) {
111
-        initHTML();
112
-
113
-        // LANGUAGES BOX
114
-        if (UIUtil.isSettingEnabled('language')) {
115
-            const wrapperId = 'languagesSelectWrapper';
116
-            const selectId = 'languagesSelect';
117
-            const selectEl = AJS.$(`#${selectId}`);
118
-            let selectInput; // eslint-disable-line prefer-const
119
-
120
-            selectEl.html(generateLanguagesOptions(
121
-                LANGUAGES,
122
-                APP.translation.getCurrentLanguage()
123
-            ));
124
-            initSelect2(selectEl, () => {
125
-                const val = selectEl.val();
126
-
127
-                selectInput[0].dataset.i18n = `languages:${val}`;
128
-                APP.translation.translateElement(selectInput);
129
-                emitter.emit(UIEvents.LANG_CHANGED, val);
130
-            });
131
-
132
-            // find new selectInput element
133
-            selectInput = $(`#s2id_${selectId} .select2-chosen`);
134
-
135
-            // first select fix for languages options
136
-            selectInput[0].dataset.i18n
137
-                = `languages:${APP.translation.getCurrentLanguage()}`;
138
-
139
-            // translate selectInput, which is the currently selected language
140
-            // otherwise there will be no selected option
141
-            APP.translation.translateElement(selectInput);
142
-            APP.translation.translateElement(selectEl);
143
-
144
-            APP.translation.addLanguageChangedListener(
145
-                lng => {
146
-                    selectInput[0].dataset.i18n = `languages:${lng}`;
147
-                });
148
-
149
-            UIUtil.setVisible(wrapperId, true);
150
-        }
151
-
152
-        // DEVICES LIST
153
-        if (UIUtil.isSettingEnabled('devices')) {
154
-            const wrapperId = 'deviceOptionsWrapper';
16
+    init() {
17
+        const settingsMenuContainer = document.createElement('div');
155 18
 
156
-            $('#deviceSelection').on('click', () =>
157
-                APP.store.dispatch(openDeviceSelectionDialog()));
19
+        settingsMenuContainer.id = 'settings_container';
20
+        settingsMenuContainer.className = 'sideToolbarContainer__inner';
158 21
 
159
-            // Only show the subtitle if this isn't the only setting section.
160
-            if (interfaceConfig.SETTINGS_SECTIONS.length > 1) {
161
-                UIUtil.setVisible('deviceOptionsTitle', true);
162
-            }
22
+        $('#sideToolbarContainer').append(settingsMenuContainer);
163 23
 
164
-            UIUtil.setVisible(wrapperId, true);
165
-        }
166
-
167
-        // MODERATOR
168
-        if (UIUtil.isSettingEnabled('moderator')) {
169
-            const wrapperId = 'moderatorOptionsWrapper';
170
-
171
-            // START MUTED
172
-            $('#startMutedOptions').change(() => {
173
-                const startAudioMuted = $('#startAudioMuted').is(':checked');
174
-                const startVideoMuted = $('#startVideoMuted').is(':checked');
175
-
176
-                emitter.emit(
177
-                    UIEvents.START_MUTED_CHANGED,
178
-                    startAudioMuted,
179
-                    startVideoMuted
180
-                );
181
-            });
182
-
183
-            // FOLLOW ME
184
-            const followMeToggle = document.getElementById('followMeCheckBox');
185
-
186
-            followMeToggle.addEventListener('change', () => {
187
-                const isFollowMeEnabled = followMeToggle.checked;
188
-
189
-                emitter.emit(UIEvents.FOLLOW_ME_ENABLED, isFollowMeEnabled);
190
-            });
191
-
192
-            UIUtil.setVisible(wrapperId, true);
193
-        }
194
-    },
195
-
196
-    /**
197
-     * If start audio muted/start video muted options should be visible or not.
198
-     * @param {boolean} show
199
-     */
200
-    showStartMutedOptions(show) {
201
-        if (show && UIUtil.isSettingEnabled('moderator')) {
202
-            // Only show the subtitle if this isn't the only setting section.
203
-            if (!$('#moderatorOptionsTitle').is(':visible')
204
-                    && interfaceConfig.SETTINGS_SECTIONS.length > 1) {
205
-                UIUtil.setVisible('moderatorOptionsTitle', true);
206
-            }
207
-
208
-            UIUtil.setVisible('startMutedOptions', true);
209
-        } else {
210
-            // Only show the subtitle if this isn't the only setting section.
211
-            if ($('#moderatorOptionsTitle').is(':visible')) {
212
-                UIUtil.setVisible('moderatorOptionsTitle', false);
213
-            }
214
-
215
-            UIUtil.setVisible('startMutedOptions', false);
216
-        }
217
-    },
218
-
219
-    updateStartMutedBox(startAudioMuted, startVideoMuted) {
220
-        $('#startAudioMuted').attr('checked', startAudioMuted);
221
-        $('#startVideoMuted').attr('checked', startVideoMuted);
222
-    },
24
+        const props = {
25
+            showDeviceSettings: UIUtil.isSettingEnabled('devices'),
26
+            showLanguageSettings: UIUtil.isSettingEnabled('language'),
27
+            showModeratorSettings: UIUtil.isSettingEnabled('moderator'),
28
+            showTitles: interfaceConfig.SETTINGS_SECTIONS.length > 1
29
+        };
223 30
 
224
-    /**
225
-     * Shows/hides the follow me options in the settings dialog.
226
-     *
227
-     * @param {boolean} show {true} to show those options, {false} to hide them
228
-     */
229
-    showFollowMeOptions(show) {
230
-        UIUtil.setVisible(
231
-            'followMeOptions',
232
-            show && UIUtil.isSettingEnabled('moderator'));
31
+        ReactDOM.render(
32
+            <Provider store = { APP.store }>
33
+                <I18nextProvider i18n = { i18next }>
34
+                    <SettingsMenu
35
+                        { ...props } />
36
+                </I18nextProvider>
37
+            </Provider>,
38
+            settingsMenuContainer
39
+        );
233 40
     },
234 41
 
235 42
     /**

+ 2
- 21
modules/translation/translation.js 查看文件

@@ -2,7 +2,7 @@
2 2
 
3 3
 import jqueryI18next from 'jquery-i18next';
4 4
 
5
-import { DEFAULT_LANGUAGE, i18next } from '../../react/features/base/i18n';
5
+import { i18next } from '../../react/features/base/i18n';
6 6
 
7 7
 declare var $: Function;
8 8
 
@@ -20,13 +20,6 @@ function _onI18nInitialized() {
20 20
  *
21 21
  */
22 22
 class Translation {
23
-    /**
24
-     *
25
-     */
26
-    addLanguageChangedListener(listener: Function) {
27
-        i18next.on('languageChanged', listener);
28
-    }
29
-
30 23
     /**
31 24
      *
32 25
      */
@@ -40,13 +33,6 @@ class Translation {
40 33
         return `<span data-i18n="${key}"${optAttr}>${text}</span>`;
41 34
     }
42 35
 
43
-    /**
44
-     *
45
-     */
46
-    getCurrentLanguage() {
47
-        return i18next.lng();
48
-    }
49
-
50 36
     /**
51 37
      *
52 38
      */
@@ -58,13 +44,8 @@ class Translation {
58 44
         } else {
59 45
             i18next.on('initialized', _onI18nInitialized);
60 46
         }
61
-    }
62 47
 
63
-    /**
64
-     *
65
-     */
66
-    setLanguage(language: string = DEFAULT_LANGUAGE) {
67
-        i18next.setLng(language, {}, _onI18nInitialized);
48
+        i18next.on('languageChanged', _onI18nInitialized);
68 49
     }
69 50
 
70 51
     /**

+ 23
- 0
react/features/base/conference/actionTypes.js 查看文件

@@ -96,6 +96,17 @@ export const P2P_STATUS_CHANGED = Symbol('P2P_STATUS_CHANGED');
96 96
  */
97 97
 export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
98 98
 
99
+/**
100
+ * The type of (redux) action which updates the current known status of the
101
+ * Follow Me feature.
102
+ *
103
+ * {
104
+ *     type: SET_FOLLOW_ME,
105
+ *     enabled: boolean
106
+ * }
107
+ */
108
+export const SET_FOLLOW_ME = Symbol('SET_FOLLOW_ME');
109
+
99 110
 /**
100 111
  * The type of (redux) action which sets the video channel's lastN (value).
101 112
  *
@@ -162,3 +173,15 @@ export const SET_ROOM = Symbol('SET_ROOM');
162 173
  * }
163 174
  */
164 175
 export const SET_SIP_GATEWAY_ENABLED = Symbol('SET_SIP_GATEWAY_ENABLED');
176
+
177
+/**
178
+ * The type of (redux) action which updates the current known status of the
179
+ * moderator features for starting participants as audio or video muted.
180
+ *
181
+ * {
182
+ *     type: SET_START_MUTED_POLICY,
183
+ *     startAudioMutedPolicy: boolean,
184
+ *     startVideoMutedPolicy: boolean
185
+ * }
186
+ */
187
+export const SET_START_MUTED_POLICY = Symbol('SET_START_MUTED_POLICY');

+ 73
- 1
react/features/base/conference/actions.js 查看文件

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import UIEvents from '../../../../service/UI/UIEvents';
4
+
3 5
 import { sendAnalyticsEvent } from '../../analytics';
4 6
 import { getName } from '../../app';
5 7
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
@@ -24,11 +26,13 @@ import {
24 26
     LOCK_STATE_CHANGED,
25 27
     P2P_STATUS_CHANGED,
26 28
     SET_AUDIO_ONLY,
29
+    SET_FOLLOW_ME,
27 30
     SET_LASTN,
28 31
     SET_PASSWORD,
29 32
     SET_PASSWORD_FAILED,
30 33
     SET_RECEIVE_VIDEO_QUALITY,
31
-    SET_ROOM
34
+    SET_ROOM,
35
+    SET_START_MUTED_POLICY
32 36
 } from './actionTypes';
33 37
 import {
34 38
     AVATAR_ID_COMMAND,
@@ -45,6 +49,8 @@ import type { Dispatch } from 'redux';
45 49
 
46 50
 const logger = require('jitsi-meet-logger').getLogger(__filename);
47 51
 
52
+declare var APP: Object;
53
+
48 54
 /**
49 55
  * Adds conference (event) listeners.
50 56
  *
@@ -362,6 +368,28 @@ export function lockStateChanged(conference: Object, locked: boolean) {
362 368
     };
363 369
 }
364 370
 
371
+/**
372
+ * Updates the known state of start muted policies.
373
+ *
374
+ * @param {boolean} audioMuted - Whether or not members will join the conference
375
+ * as audio muted.
376
+ * @param {boolean} videoMuted - Whether or not members will join the conference
377
+ * as video muted.
378
+ * @returns {{
379
+ *     type: SET_START_MUTED_POLICY,
380
+ *     startAudioMutedPolicy: boolean,
381
+ *     startVideoMutedPolicy: boolean
382
+ * }}
383
+ */
384
+export function onStartMutedPolicyChanged(
385
+        audioMuted: boolean, videoMuted: boolean) {
386
+    return {
387
+        type: SET_START_MUTED_POLICY,
388
+        startAudioMutedPolicy: audioMuted,
389
+        startVideoMutedPolicy: videoMuted
390
+    };
391
+}
392
+
365 393
 /**
366 394
  * Sets whether or not peer2peer is currently enabled.
367 395
  *
@@ -395,6 +423,26 @@ export function setAudioOnly(audioOnly: boolean) {
395 423
     };
396 424
 }
397 425
 
426
+/**
427
+ * Enables or disables the Follow Me feature.
428
+ *
429
+ * @param {boolean} enabled - Whether or not Follow Me should be enabled.
430
+ * @returns {{
431
+ *     type: SET_FOLLOW_ME,
432
+ *     enabled: boolean
433
+ * }}
434
+ */
435
+export function setFollowMe(enabled: boolean) {
436
+    if (typeof APP !== 'undefined') {
437
+        APP.UI.emitEvent(UIEvents.FOLLOW_ME_ENABLED, enabled);
438
+    }
439
+
440
+    return {
441
+        type: SET_FOLLOW_ME,
442
+        enabled
443
+    };
444
+}
445
+
398 446
 /**
399 447
  * Sets the video channel's last N (value) of the current conference. A value of
400 448
  * undefined shall be used to reset it to the default value.
@@ -528,6 +576,30 @@ export function setRoom(room: ?string) {
528 576
     };
529 577
 }
530 578
 
579
+/**
580
+ * Sets whether or not members should join audio and/or video muted.
581
+ *
582
+ * @param {boolean} startAudioMuted - Whether or not members will join the
583
+ * conference as audio muted.
584
+ * @param {boolean} startVideoMuted - Whether or not members will join the
585
+ * conference as video muted.
586
+ * @returns {Function}
587
+ */
588
+export function setStartMutedPolicy(
589
+        startAudioMuted: boolean, startVideoMuted: boolean) {
590
+    return (dispatch: Dispatch<*>, getState: Function) => {
591
+        const { conference } = getState()['features/base/conference'];
592
+
593
+        conference.setStartMutedPolicy({
594
+            audio: startAudioMuted,
595
+            video: startVideoMuted
596
+        });
597
+
598
+        return dispatch(
599
+            onStartMutedPolicyChanged(startAudioMuted, startVideoMuted));
600
+    };
601
+}
602
+
531 603
 /**
532 604
  * Toggles the audio-only flag for the current JitsiConference.
533 605
  *

+ 16
- 1
react/features/base/conference/reducer.js 查看文件

@@ -14,10 +14,12 @@ import {
14 14
     LOCK_STATE_CHANGED,
15 15
     P2P_STATUS_CHANGED,
16 16
     SET_AUDIO_ONLY,
17
+    SET_FOLLOW_ME,
17 18
     SET_PASSWORD,
18 19
     SET_RECEIVE_VIDEO_QUALITY,
19 20
     SET_ROOM,
20
-    SET_SIP_GATEWAY_ENABLED
21
+    SET_SIP_GATEWAY_ENABLED,
22
+    SET_START_MUTED_POLICY
21 23
 } from './actionTypes';
22 24
 import { VIDEO_QUALITY_LEVELS } from './constants';
23 25
 import { isRoomValid } from './functions';
@@ -55,6 +57,12 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
55 57
     case SET_AUDIO_ONLY:
56 58
         return _setAudioOnly(state, action);
57 59
 
60
+    case SET_FOLLOW_ME:
61
+        return {
62
+            ...state,
63
+            followMeEnabled: action.enabled
64
+        };
65
+
58 66
     case SET_PASSWORD:
59 67
         return _setPassword(state, action);
60 68
 
@@ -66,6 +74,13 @@ ReducerRegistry.register('features/base/conference', (state = {}, action) => {
66 74
 
67 75
     case SET_SIP_GATEWAY_ENABLED:
68 76
         return _setSIPGatewayEnabled(state, action);
77
+
78
+    case SET_START_MUTED_POLICY:
79
+        return {
80
+            ...state,
81
+            startAudioMutedPolicy: action.startAudioMutedPolicy,
82
+            startVideoMutedPolicy: action.startVideoMutedPolicy
83
+        };
69 84
     }
70 85
 
71 86
     return state;

+ 0
- 0
react/features/settings-menu/components/DeviceSelectionButton.native.js 查看文件


+ 87
- 0
react/features/settings-menu/components/DeviceSelectionButton.web.js 查看文件

@@ -0,0 +1,87 @@
1
+import Button from '@atlaskit/button';
2
+import PropTypes from 'prop-types';
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { translate } from '../../base/i18n';
7
+import { openDeviceSelectionDialog } from '../../device-selection';
8
+
9
+/**
10
+ * Implements a React {@link Component} which displays a button for opening the
11
+ * {@code DeviceSelectionDialog}.
12
+ *
13
+ * @extends Component
14
+ */
15
+class DeviceSelectionButton extends Component {
16
+    /**
17
+     * {@code DeviceSelectionButton} component's property types.
18
+     *
19
+     * @static
20
+     */
21
+    static propTypes = {
22
+        /**
23
+         * Invoked to display the {@code DeviceSelectionDialog}.
24
+         */
25
+        dispatch: PropTypes.func,
26
+
27
+        /**
28
+         * Whether or not the button's title should be displayed.
29
+         */
30
+        showTitle: PropTypes.bool,
31
+
32
+        /**
33
+         * Invoked to obtain translated strings.
34
+         */
35
+        t: PropTypes.func
36
+    };
37
+
38
+    /**
39
+     * Initializes a new {@code DeviceSelectionButton} instance.
40
+     *
41
+     * @param {Object} props - The read-only properties with which the new
42
+     * instance is to be initialized.
43
+     */
44
+    constructor(props) {
45
+        super(props);
46
+
47
+        // Bind event handler so it is only bound once for every instance.
48
+        this._onOpenDeviceSelectionDialog
49
+            = this._onOpenDeviceSelectionDialog.bind(this);
50
+    }
51
+
52
+    /**
53
+     * Implements React's {@link Component#render()}.
54
+     *
55
+     * @inheritdoc
56
+     * @returns {ReactElement}
57
+     */
58
+    render() {
59
+        return (
60
+            <div>
61
+                { this.props.showTitle
62
+                    ? <div className = 'subTitle'>
63
+                        { this.props.t('settings.audioVideo') }
64
+                    </div>
65
+                    : null }
66
+                <Button
67
+                    appearance = 'primary'
68
+                    onClick = { this._onOpenDeviceSelectionDialog }
69
+                    shouldFitContainer = { true }>
70
+                    { this.props.t('deviceSelection.deviceSettings') }
71
+                </Button>
72
+            </div>
73
+        );
74
+    }
75
+
76
+    /**
77
+     * Opens the {@code DeviceSelectionDialog}.
78
+     *
79
+     * @private
80
+     * @returns {void}
81
+     */
82
+    _onOpenDeviceSelectionDialog() {
83
+        this.props.dispatch(openDeviceSelectionDialog());
84
+    }
85
+}
86
+
87
+export default translate(connect()(DeviceSelectionButton));

+ 0
- 0
react/features/settings-menu/components/LanguageSelectDropdown.native.js 查看文件


+ 179
- 0
react/features/settings-menu/components/LanguageSelectDropdown.web.js 查看文件

@@ -0,0 +1,179 @@
1
+import DropdownMenu, {
2
+    DropdownItem,
3
+    DropdownItemGroup
4
+} from '@atlaskit/dropdown-menu';
5
+import PropTypes from 'prop-types';
6
+import React, { Component } from 'react';
7
+
8
+import { DEFAULT_LANGUAGE, LANGUAGES, translate } from '../../base/i18n';
9
+
10
+/**
11
+ * Implements a React {@link Component} which displays a dropdown for changing
12
+ * application text to another language.
13
+ *
14
+ * @extends Component
15
+ */
16
+class LanguageSelectDropdown extends Component {
17
+    /**
18
+     * {@code LanguageSelectDropdown} component's property types.
19
+     *
20
+     * @static
21
+     */
22
+    static propTypes = {
23
+        /**
24
+         * The translation service.
25
+         */
26
+        i18n: PropTypes.object,
27
+
28
+        /**
29
+         * Invoked to obtain translated strings.
30
+         */
31
+        t: PropTypes.func
32
+    };
33
+
34
+
35
+    /**
36
+     * {@code LanguageSelectDropdown} component's local state.
37
+     *
38
+     * @type {Object}
39
+     * @property {string|null} currentLanguage - The currently selected language
40
+     * the application should be displayed in.
41
+     * @property {boolean} isLanguageSelectOpen - Whether or not the dropdown
42
+     * should be displayed as open.
43
+     */
44
+    state = {
45
+        currentLanguage: null,
46
+        isLanguageSelectOpen: false
47
+    };
48
+
49
+    /**
50
+     * Initializes a new {@code LanguageSelectDropdown} instance.
51
+     *
52
+     * @param {Object} props - The read-only properties with which the new
53
+     * instance is to be initialized.
54
+     */
55
+    constructor(props) {
56
+        super(props);
57
+
58
+        this.state.currentLanguage
59
+            = this.props.i18n.language || DEFAULT_LANGUAGE;
60
+
61
+        // Bind event handlers so they are only bound once for every instance.
62
+        this._onLanguageSelected = this._onLanguageSelected.bind(this);
63
+        this._onSetDropdownOpen = this._onSetDropdownOpen.bind(this);
64
+        this._setCurrentLanguage = this._setCurrentLanguage.bind(this);
65
+    }
66
+
67
+    /**
68
+     * Sets a listener to update the currently selected language if it is
69
+     * changed from somewhere else.
70
+     *
71
+     * @inheritdoc
72
+     * @returns {void}
73
+     */
74
+    componentDidMount() {
75
+        this.props.i18n.on('languageChanged', this._setCurrentLanguage);
76
+    }
77
+
78
+    /**
79
+     * Removes all listeners.
80
+     *
81
+     * @inheritdoc
82
+     * @returns {void}
83
+     */
84
+    componentWillUnmount() {
85
+        this.props.i18n.off('languageChanged', this._setCurrentLanguage);
86
+    }
87
+
88
+    /**
89
+     * Implements React's {@link Component#render()}.
90
+     *
91
+     * @inheritdoc
92
+     * @returns {ReactElement}
93
+     */
94
+    render() {
95
+        const { t } = this.props;
96
+        const { currentLanguage } = this.state;
97
+
98
+        const languageItems = LANGUAGES.map(language =>
99
+            // eslint-disable-next-line react/jsx-wrap-multilines
100
+            <DropdownItem
101
+                key = { language }
102
+                // eslint-disable-next-line react/jsx-no-bind
103
+                onClick = { () => this._onLanguageSelected(language) }>
104
+                { t(`languages:${language}`) }
105
+            </DropdownItem>
106
+        );
107
+
108
+        return (
109
+            <div>
110
+                <DropdownMenu
111
+                    isOpen = { this.state.isLanguageSelectOpen }
112
+                    onOpenChange = { this._onSetDropdownOpen }
113
+                    shouldFitContainer = { true }
114
+                    trigger = { currentLanguage
115
+                        ? t(`languages:${currentLanguage}`)
116
+                        : '' }
117
+                    triggerButtonProps = {{
118
+                        appearance: 'primary',
119
+                        shouldFitContainer: true
120
+                    }}
121
+                    triggerType = 'button'>
122
+                    <DropdownItemGroup>
123
+                        { languageItems }
124
+                    </DropdownItemGroup>
125
+                </DropdownMenu>
126
+            </div>
127
+        );
128
+    }
129
+
130
+    /**
131
+     * Updates the application's currently displayed language.
132
+     *
133
+     * @param {string} language - The language code for the language to display.
134
+     * @private
135
+     * @returns {void}
136
+     */
137
+    _onLanguageSelected(language) {
138
+        const previousLanguage = this.state.currentLanguage;
139
+
140
+        this.setState({
141
+            currentLanguage: language,
142
+            isLanguageSelectOpen: false
143
+        });
144
+
145
+        this.props.i18n.changeLanguage(language, error => {
146
+            if (error) {
147
+                this._setCurrentLanguage(previousLanguage);
148
+            }
149
+        });
150
+    }
151
+
152
+    /**
153
+     * Set whether or not the dropdown should be open.
154
+     *
155
+     * @param {Object} dropdownEvent - The event returned from requesting the
156
+     * open state of the dropdown be changed.
157
+     * @private
158
+     * @returns {void}
159
+     */
160
+    _onSetDropdownOpen(dropdownEvent) {
161
+        this.setState({
162
+            isLanguageSelectOpen: dropdownEvent.isOpen
163
+        });
164
+    }
165
+
166
+    /**
167
+     * Updates the known current language of the application.
168
+     *
169
+     * @param {string} currentLanguage - The language code for the current
170
+     * language.
171
+     * @private
172
+     * @returns {void}
173
+     */
174
+    _setCurrentLanguage(currentLanguage) {
175
+        this.setState({ currentLanguage });
176
+    }
177
+}
178
+
179
+export default translate(LanguageSelectDropdown);

+ 0
- 0
react/features/settings-menu/components/ModeratorCheckboxes.native.js 查看文件


+ 199
- 0
react/features/settings-menu/components/ModeratorCheckboxes.web.js 查看文件

@@ -0,0 +1,199 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { setFollowMe, setStartMutedPolicy } from '../../base/conference';
6
+import { translate } from '../../base/i18n';
7
+
8
+/**
9
+ * Implements a React {@link Component} which displays checkboxes for enabling
10
+ * and disabling moderator-only conference features.
11
+ *
12
+ * @extends Component
13
+ */
14
+class ModeratorCheckboxes extends Component {
15
+    /**
16
+     * {@code ModeratorCheckboxes} component's property types.
17
+     *
18
+     * @static
19
+     */
20
+    static propTypes = {
21
+        /**
22
+         * Whether or not the Follow Me feature is currently enabled.
23
+         */
24
+        _followMeEnabled: PropTypes.bool,
25
+
26
+        /**
27
+         * Whether or not new members will join the conference as audio muted.
28
+         */
29
+        _startAudioMutedPolicy: PropTypes.bool,
30
+
31
+        /**
32
+         * Whether or note new member will join the conference as video muted.
33
+         */
34
+        _startVideoMutedPolicy: PropTypes.bool,
35
+
36
+        /**
37
+         * Invoked to enable and disable moderator-only conference features.
38
+         */
39
+        dispatch: PropTypes.func,
40
+
41
+        /**
42
+         * Whether or not the title should be displayed.
43
+         */
44
+        showTitle: PropTypes.bool,
45
+
46
+        /**
47
+         * Invokted to obtain translated strings.
48
+         */
49
+        t: PropTypes.func
50
+    };
51
+
52
+    /**
53
+     * Initializes a new {@code ModeratorCheckboxes} instance.
54
+     *
55
+     * @param {Object} props - The read-only properties with which the new
56
+     * instance is to be initialized.
57
+     */
58
+    constructor(props) {
59
+        super(props);
60
+
61
+        // Bind event handlers so they are only bound once for every instance.
62
+        this._onSetFollowMeSetting
63
+            = this._onSetFollowMeSetting.bind(this);
64
+        this._onSetStartAudioMutedPolicy
65
+            = this._onSetStartAudioMutedPolicy.bind(this);
66
+        this._onSetStartVideoMutedPolicy
67
+            = this._onSetStartVideoMutedPolicy.bind(this);
68
+    }
69
+
70
+    /**
71
+     * Implements React's {@link Component#render()}.
72
+     *
73
+     * @inheritdoc
74
+     * @returns {ReactElement}
75
+     */
76
+    render() {
77
+        const {
78
+            _followMeEnabled,
79
+            _startAudioMutedPolicy,
80
+            _startVideoMutedPolicy,
81
+            showTitle,
82
+            t
83
+        } = this.props;
84
+
85
+        return (
86
+            <div>
87
+                { showTitle
88
+                    ? <div className = 'subTitle'>
89
+                        { t('settings.moderator') }
90
+                    </div>
91
+                    : null }
92
+                <div className = 'moderator-option'>
93
+                    <input
94
+                        checked = { _startAudioMutedPolicy }
95
+                        className = 'moderator-checkbox'
96
+                        id = 'startAudioMuted'
97
+                        onChange = { this._onSetStartAudioMutedPolicy }
98
+                        type = 'checkbox' />
99
+                    <label
100
+                        className = 'moderator-checkbox-label'
101
+                        htmlFor = 'startAudioMuted'>
102
+                        { t('settings.startAudioMuted') }
103
+                    </label>
104
+                </div>
105
+                <div className = 'moderator-option'>
106
+                    <input
107
+                        checked = { _startVideoMutedPolicy }
108
+                        className = 'moderator-checkbox'
109
+                        id = 'startVideoMuted'
110
+                        onChange = { this._onSetStartVideoMutedPolicy }
111
+                        type = 'checkbox' />
112
+                    <label
113
+                        className = 'moderator-checkbox-label'
114
+                        htmlFor = 'startVideoMuted'>
115
+                        { t('settings.startVideoMuted') }
116
+                    </label>
117
+                </div>
118
+                <div className = 'moderator-option'>
119
+                    <input
120
+                        checked = { _followMeEnabled }
121
+                        className = 'moderator-checkbox'
122
+                        id = 'followMeCheckBox'
123
+                        onChange = { this._onSetFollowMeSetting }
124
+                        type = 'checkbox' />
125
+                    <label
126
+                        className = 'moderator-checkbox-label'
127
+                        htmlFor = 'followMeCheckBox'>
128
+                        { t('settings.followMe') }
129
+                    </label>
130
+                </div>
131
+            </div>
132
+        );
133
+    }
134
+
135
+    /**
136
+     * Toggles the Follow Me feature.
137
+     *
138
+     * @param {Object} event - The dom event returned from changes the checkbox.
139
+     * @private
140
+     * @returns {void}
141
+     */
142
+    _onSetFollowMeSetting(event) {
143
+        this.props.dispatch(setFollowMe(event.target.checked));
144
+    }
145
+
146
+    /**
147
+     * Toggles whether or not new members should join the conference as audio
148
+     * muted.
149
+     *
150
+     * @param {Object} event - The dom event returned from changes the checkbox.
151
+     * @private
152
+     * @returns {void}
153
+     */
154
+    _onSetStartAudioMutedPolicy(event) {
155
+        this.props.dispatch(setStartMutedPolicy(
156
+            event.target.checked, this.props._startVideoMutedPolicy));
157
+    }
158
+
159
+    /**
160
+     * Toggles whether or not new members should join the conference as video
161
+     * muted.
162
+     *
163
+     * @param {Object} event - The dom event returned from changes the checkbox.
164
+     * @private
165
+     * @returns {void}
166
+     */
167
+    _onSetStartVideoMutedPolicy(event) {
168
+        this.props.dispatch(setStartMutedPolicy(
169
+            this.props._startAudioMutedPolicy, event.target.checked));
170
+    }
171
+}
172
+
173
+/**
174
+ * Maps (parts of) the Redux state to the associated props for the
175
+ * {@code ModeratorCheckboxes} component.
176
+ *
177
+ * @param {Object} state - The Redux state.
178
+ * @private
179
+ * @returns {{
180
+ *     _followMeEnabled: boolean,
181
+ *     _startAudioMutedPolicy: boolean,
182
+ *     _startVideoMutedPolicy: boolean
183
+ * }}
184
+ */
185
+function _mapStateToProps(state) {
186
+    const {
187
+        followMeEnabled,
188
+        startAudioMutedPolicy,
189
+        startVideoMutedPolicy
190
+    } = state['features/base/conference'];
191
+
192
+    return {
193
+        _followMeEnabled: Boolean(followMeEnabled),
194
+        _startAudioMutedPolicy: Boolean(startAudioMutedPolicy),
195
+        _startVideoMutedPolicy: Boolean(startVideoMutedPolicy)
196
+    };
197
+}
198
+
199
+export default translate(connect(_mapStateToProps)(ModeratorCheckboxes));

+ 0
- 0
react/features/settings-menu/components/SettingsMenu.native.js 查看文件


+ 108
- 0
react/features/settings-menu/components/SettingsMenu.web.js 查看文件

@@ -0,0 +1,108 @@
1
+import PropTypes from 'prop-types';
2
+import React, { Component } from 'react';
3
+import { connect } from 'react-redux';
4
+
5
+import { translate } from '../../base/i18n';
6
+import { getLocalParticipant, PARTICIPANT_ROLE } from '../../base/participants';
7
+
8
+import DeviceSelectionButton from './DeviceSelectionButton';
9
+import LanguageSelectDropdown from './LanguageSelectDropdown';
10
+import ModeratorCheckboxes from './ModeratorCheckboxes';
11
+
12
+/**
13
+ * Implements a React {@link Component} which various ways to change application
14
+ * settings.
15
+ *
16
+ * @extends Component
17
+ */
18
+class SettingsMenu extends Component {
19
+    /**
20
+     * {@code SettingsMenu} component's property types.
21
+     *
22
+     * @static
23
+     */
24
+    static propTypes = {
25
+        /**
26
+         * Whether or not the local user is a moderator.
27
+         */
28
+        _isModerator: PropTypes.bool,
29
+
30
+        /**
31
+         * Whether or not the button to open device selection should display.
32
+         */
33
+        showDeviceSettings: PropTypes.bool,
34
+
35
+        /**
36
+         * Whether or not the dropdown to change the current translated language
37
+         * should display.
38
+         */
39
+        showLanguageSettings: PropTypes.bool,
40
+
41
+        /**
42
+         * Whether or not moderator-only actions that affect the conference
43
+         * should display.
44
+         */
45
+        showModeratorSettings: PropTypes.bool,
46
+
47
+        /**
48
+         * Whether or not menu section should have section titles displayed.
49
+         */
50
+        showTitles: PropTypes.bool,
51
+
52
+        /**
53
+         * Invoked to obtain translated strings.
54
+         */
55
+        t: PropTypes.func
56
+    };
57
+
58
+    /**
59
+     * Implements React's {@link Component#render()}.
60
+     *
61
+     * @inheritdoc
62
+     * @returns {ReactElement}
63
+     */
64
+    render() {
65
+        const {
66
+            _isModerator,
67
+            showDeviceSettings,
68
+            showLanguageSettings,
69
+            showModeratorSettings,
70
+            showTitles,
71
+            t
72
+        } = this.props;
73
+
74
+        return (
75
+            <div className = 'settings-menu'>
76
+                <div className = 'title'>
77
+                    { t('settings.title') }
78
+                </div>
79
+                { showLanguageSettings
80
+                    ? <LanguageSelectDropdown />
81
+                    : null }
82
+                { showDeviceSettings
83
+                    ? <DeviceSelectionButton showTitle = { showTitles } />
84
+                    : null }
85
+                { _isModerator && showModeratorSettings
86
+                    ? <ModeratorCheckboxes showTitle = { showTitles } />
87
+                    : null }
88
+            </div>
89
+        );
90
+    }
91
+}
92
+
93
+/**
94
+ * Maps parts of Redux store to component prop types.
95
+ *
96
+ * @param {Object} state - Snapshot of Redux store.
97
+ * @returns {{
98
+ *      _isModerator: boolean
99
+ * }}
100
+ */
101
+function _mapStateToProps(state) {
102
+    return {
103
+        _isModerator:
104
+            getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR
105
+    };
106
+}
107
+
108
+export default translate(connect(_mapStateToProps)(SettingsMenu));

+ 1
- 0
react/features/settings-menu/components/index.js 查看文件

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

+ 1
- 0
react/features/settings-menu/index.js 查看文件

@@ -0,0 +1 @@
1
+export * from './components';

+ 0
- 6
service/UI/UIEvents.js 查看文件

@@ -8,11 +8,6 @@ export default {
8 8
      */
9 9
     MESSAGE_CREATED: 'UI.message_created',
10 10
 
11
-    /**
12
-     * Notifies that local user changed language.
13
-     */
14
-    LANG_CHANGED: 'UI.lang_changed',
15
-
16 11
     /**
17 12
      * Notifies that local user changed email.
18 13
      */
@@ -21,7 +16,6 @@ export default {
21 16
     /**
22 17
      * Notifies that "start muted" settings changed.
23 18
      */
24
-    START_MUTED_CHANGED: 'UI.start_muted_changed',
25 19
     AUDIO_MUTED: 'UI.audio_muted',
26 20
     VIDEO_MUTED: 'UI.video_muted',
27 21
     VIDEO_UNMUTING_WHILE_AUDIO_ONLY: 'UI.video_unmuting_while_audio_only',

Loading…
取消
儲存