ソースを参照

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.
master
Leonard Kim 8年前
コミット
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',

読み込み中…
キャンセル
保存