Quellcode durchsuchen

feat(video-label): Add dropdown for toggling audio only

Add a menu that displays when hovering over VideoStatusLabel. The menu's
display is controlled by CSS. As the existing AudioOnlyLabel no longer needs
needs its own tooltip, it has been removed and label display logic has been
moved into VideoStatusLabel.
j8
Leonard Kim vor 8 Jahren
Ursprung
Commit
80989147ad

+ 55
- 1
css/_videolayout_default.scss Datei anzeigen

@@ -543,4 +543,58 @@
543 543
 
544 544
 .moveToCorner + .moveToCorner {
545 545
     right: 80px;
546
-}
546
+}
547
+
548
+.video-state-indicator-menu {
549
+    display: none;
550
+    padding: 10px;
551
+    position: absolute;
552
+    right: -10px;
553
+    top: 20px;
554
+
555
+    .video-state-indicator-menu-options {
556
+        background: $popoverBg;
557
+        border-radius: 3px;
558
+        color: $popoverFontColor;
559
+        margin-top: 20px;
560
+        padding: 5px 0;
561
+        position: relative;
562
+
563
+        div {
564
+            cursor: pointer;
565
+            padding: 10px;
566
+            padding-right: 30px;
567
+            text-align: left;
568
+            white-space: nowrap;
569
+
570
+            &.active {
571
+                background: $toolbarToggleBackground;
572
+            }
573
+            &:hover:not(.active) {
574
+                background: $popupMenuSelectedItemBackground;
575
+            }
576
+
577
+            i {
578
+                margin-right: 5px;
579
+                vertical-align: middle;
580
+            }
581
+        }
582
+    }
583
+
584
+    .video-state-indicator-menu-options::after {
585
+        content: " ";
586
+        border-color: transparent transparent $popoverBg transparent;
587
+        border-style: solid;
588
+        border-width: 5px;
589
+        position: absolute;
590
+        right: 15px;
591
+        top: -10px;
592
+    }
593
+}
594
+
595
+.video-state-indicator:hover,
596
+.video-state-indicator *:hover {
597
+    .video-state-indicator-menu {
598
+        display: block;
599
+    }
600
+}

+ 7
- 2
lang/main.json Datei anzeigen

@@ -16,8 +16,7 @@
16 16
     "callingName": "__name__",
17 17
     "audioOnly": {
18 18
         "audioOnly": "Audio only",
19
-        "featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode",
20
-        "howToDisable": "Audio only mode is currently enabled. Click the audio only button in the toolbar to disable the feature."
19
+        "featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode"
21 20
     },
22 21
     "userMedia": {
23 22
       "react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
@@ -447,5 +446,11 @@
447 446
         "numbersDisabled": "Dialing in has been disabled",
448 447
         "showPassword": "Show password",
449 448
         "unlocked": "This call is unlocked. Any new caller with the link may join the call."
449
+    },
450
+    "videoStatus": {
451
+        "hd": "HD",
452
+        "hdVideo": "HD video",
453
+        "sd": "SD",
454
+        "sdVideo": "SD video"
450 455
     }
451 456
 }

+ 1
- 4
react/features/base/conference/reducer.js Datei anzeigen

@@ -242,10 +242,7 @@ function _lockStateChanged(state, action) {
242 242
  * reduction of the specified action.
243 243
  */
244 244
 function _setAudioOnly(state, action) {
245
-    return assign(state, {
246
-        audioOnly: action.audioOnly,
247
-        isLargeVideoHD: action.audioOnly ? false : state.isLargeVideoHD
248
-    });
245
+    return set(state, 'audioOnly', action.audioOnly);
249 246
 }
250 247
 
251 248
 /**

+ 0
- 104
react/features/video-status-label/components/AudioOnlyLabel.js Datei anzeigen

@@ -1,104 +0,0 @@
1
-import React, { Component } from 'react';
2
-
3
-import UIUtil from '../../../../modules/UI/util/UIUtil';
4
-
5
-import { translate } from '../../base/i18n';
6
-
7
-/**
8
- * React {@code Component} for displaying a message to indicate audio only mode
9
- * is active and for triggering a tooltip to provide more information about
10
- * audio only mode.
11
- *
12
- * @extends Component
13
- */
14
-export class AudioOnlyLabel extends Component {
15
-    /**
16
-     * {@code AudioOnlyLabel}'s property types.
17
-     *
18
-     * @static
19
-     */
20
-    static propTypes = {
21
-        /**
22
-         * Invoked to obtain translated strings.
23
-         */
24
-        t: React.PropTypes.func
25
-    }
26
-
27
-    /**
28
-     * Initializes a new {@code AudioOnlyLabel} instance.
29
-     *
30
-     * @param {Object} props - The read-only properties with which the new
31
-     * instance is to be initialized.
32
-     */
33
-    constructor(props) {
34
-        super(props);
35
-
36
-        /**
37
-         * The internal reference to the DOM/HTML element at the top of the
38
-         * React {@code Component}'s DOM/HTML hierarchy. It is necessary for
39
-         * setting a tooltip to display when hovering over the component.
40
-         *
41
-         * @private
42
-         * @type {HTMLDivElement}
43
-         */
44
-        this._rootElement = null;
45
-
46
-        // Bind event handlers so they are only bound once for every instance.
47
-        this._setRootElement = this._setRootElement.bind(this);
48
-    }
49
-
50
-    /**
51
-     * Sets a tooltip on the component to display on hover.
52
-     *
53
-     * @inheritdoc
54
-     * @returns {void}
55
-     */
56
-    componentDidMount() {
57
-        this._setTooltip();
58
-    }
59
-
60
-    /**
61
-     * Implements React's {@link Component#render()}.
62
-     *
63
-     * @inheritdoc
64
-     * @returns {ReactElement}
65
-     */
66
-    render() {
67
-        return (
68
-            <div
69
-                className = 'audio-only-label moveToCorner'
70
-                ref = { this._setRootElement }>
71
-                <i className = 'icon-visibility-off' />
72
-            </div>
73
-        );
74
-    }
75
-
76
-    /**
77
-     * Sets the instance variable for the component's root element so it can be
78
-     * accessed directly.
79
-     *
80
-     * @param {HTMLDivElement} element - The topmost DOM element of the
81
-     * component's DOM/HTML hierarchy.
82
-     * @private
83
-     * @returns {void}
84
-     */
85
-    _setRootElement(element) {
86
-        this._rootElement = element;
87
-    }
88
-
89
-    /**
90
-     * Sets the tooltip on the component's root element.
91
-     *
92
-     * @private
93
-     * @returns {void}
94
-     */
95
-    _setTooltip() {
96
-        UIUtil.setTooltip(
97
-            this._rootElement,
98
-            'audioOnly.howToDisable',
99
-            'left'
100
-        );
101
-    }
102
-}
103
-
104
-export default translate(AudioOnlyLabel);

+ 0
- 16
react/features/video-status-label/components/HDVideoLabel.js Datei anzeigen

@@ -1,16 +0,0 @@
1
-import React from 'react';
2
-
3
-/**
4
- * A functional React {@code Component} for showing an HD status label.
5
- *
6
- * @returns {ReactElement}
7
- */
8
-export default function HDVideoLabel() {
9
-    return (
10
-        <span
11
-            className = 'video-state-indicator moveToCorner'
12
-            id = 'videoResolutionLabel'>
13
-            HD
14
-        </span>
15
-    );
16
-}

+ 110
- 12
react/features/video-status-label/components/VideoStatusLabel.js Datei anzeigen

@@ -1,8 +1,8 @@
1 1
 import React, { Component } from 'react';
2 2
 import { connect } from 'react-redux';
3 3
 
4
-import AudioOnlyLabel from './AudioOnlyLabel';
5
-import HDVideoLabel from './HDVideoLabel';
4
+import { toggleAudioOnly } from '../../base/conference';
5
+import { translate } from '../../base/i18n';
6 6
 
7 7
 /**
8 8
  * React {@code Component} responsible for displaying a label that indicates
@@ -23,26 +23,118 @@ export class VideoStatusLabel extends Component {
23 23
          */
24 24
         _audioOnly: React.PropTypes.bool,
25 25
 
26
+        /**
27
+         * Whether or not a connection to a conference has been established.
28
+         */
29
+        _conferenceStarted: React.PropTypes.bool,
30
+
26 31
         /**
27 32
          * Whether or not a high-definition large video is displayed.
28 33
          */
29
-        _largeVideoHD: React.PropTypes.bool
34
+        _largeVideoHD: React.PropTypes.bool,
35
+
36
+        /**
37
+         * Invoked to request toggling of audio only mode.
38
+         */
39
+        dispatch: React.PropTypes.func,
40
+
41
+        /**
42
+         * Invoked to obtain translated strings.
43
+         */
44
+        t: React.PropTypes.func
45
+    }
46
+
47
+    /**
48
+     * Initializes a new {@code VideoStatusLabel} instance.
49
+     *
50
+     * @param {Object} props - The read-only React Component props with which
51
+     * the new instance is to be initialized.
52
+     */
53
+    constructor(props) {
54
+        super(props);
55
+
56
+        // Bind event handler so it is only bound once for every instance.
57
+        this._toggleAudioOnly = this._toggleAudioOnly.bind(this);
30 58
     }
31 59
 
32 60
     /**
33 61
      * Implements React's {@link Component#render()}.
34 62
      *
35 63
      * @inheritdoc
36
-     * @returns {ReactElement|null}
64
+     * @returns {ReactElement}
37 65
      */
38 66
     render() {
39
-        if (this.props._audioOnly) {
40
-            return <AudioOnlyLabel />;
41
-        } else if (this.props._largeVideoHD) {
42
-            return <HDVideoLabel />;
67
+        const { _audioOnly, _conferenceStarted, _largeVideoHD, t } = this.props;
68
+
69
+        // FIXME These truthy checks should not be necessary. The
70
+        // _conferenceStarted check is used to be defensive against toggling
71
+        // audio only mode while there is no conference and hides the need for
72
+        // error handling around audio only mode toggling. The _largeVideoHD
73
+        // check is used to prevent the label from displaying while the video
74
+        // resolution status is unknown but ties this component to the
75
+        // LargeVideoManager.
76
+        if (!_conferenceStarted || _largeVideoHD === undefined) {
77
+            return null;
78
+        }
79
+
80
+        let displayedLabel;
81
+
82
+        if (_audioOnly) {
83
+            displayedLabel = <i className = 'icon-visibility-off' />;
84
+        } else {
85
+            displayedLabel = _largeVideoHD
86
+                ? t('videoStatus.hd') : t('videoStatus.sd');
43 87
         }
44 88
 
45
-        return null;
89
+        return (
90
+            <div
91
+                className = 'video-state-indicator moveToCorner'
92
+                id = 'videoResolutionLabel' >
93
+                { displayedLabel }
94
+                { this._renderVideonMenu() }
95
+            </div>
96
+        );
97
+    }
98
+
99
+    /**
100
+     * Renders a dropdown menu for changing video modes.
101
+     *
102
+     * @private
103
+     * @returns {ReactElement}
104
+     */
105
+    _renderVideonMenu() {
106
+        const { _audioOnly, t } = this.props;
107
+        const audioOnlyAttributes = _audioOnly ? { className: 'active' }
108
+            : { onClick: this._toggleAudioOnly };
109
+        const videoAttributes = _audioOnly ? { onClick: this._toggleAudioOnly }
110
+            : { className: 'active' };
111
+
112
+        return (
113
+            <div className = 'video-state-indicator-menu'>
114
+                <div className = 'video-state-indicator-menu-options'>
115
+                    <div { ...audioOnlyAttributes }>
116
+                        <i className = 'icon-visibility' />
117
+                        { t('audioOnly.audioOnly') }
118
+                    </div>
119
+                    <div { ...videoAttributes }>
120
+                        <i className = 'icon-camera' />
121
+                        { this.props._largeVideoHD
122
+                            ? t('videoStatus.hdVideo')
123
+                            : t('videoStatus.sdVideo') }
124
+                    </div>
125
+                </div>
126
+            </div>
127
+        );
128
+    }
129
+
130
+    /**
131
+     * Dispatches an action to toggle the state of audio only mode.
132
+     *
133
+     * @private
134
+     * @returns {void}
135
+     */
136
+    _toggleAudioOnly() {
137
+        this.props.dispatch(toggleAudioOnly());
46 138
     }
47 139
 }
48 140
 
@@ -54,16 +146,22 @@ export class VideoStatusLabel extends Component {
54 146
  * @private
55 147
  * @returns {{
56 148
  *     _audioOnly: boolean,
57
- *     _largeVideoHD: boolean
149
+ *     _conferenceStarted: boolean,
150
+ *     _largeVideoHD: (boolean|undefined)
58 151
  * }}
59 152
  */
60 153
 function _mapStateToProps(state) {
61
-    const { audioOnly, isLargeVideoHD } = state['features/base/conference'];
154
+    const {
155
+        audioOnly,
156
+        conference,
157
+        isLargeVideoHD
158
+    } = state['features/base/conference'];
62 159
 
63 160
     return {
64 161
         _audioOnly: audioOnly,
162
+        _conferenceStarted: Boolean(conference),
65 163
         _largeVideoHD: isLargeVideoHD
66 164
     };
67 165
 }
68 166
 
69
-export default connect(_mapStateToProps)(VideoStatusLabel);
167
+export default translate(connect(_mapStateToProps)(VideoStatusLabel));

Laden…
Abbrechen
Speichern