Kaynağa Gözat

feat(display-name): convert to React (#1672)

* feat(display-name): convert to React

- Create a new React Component for displaying and updating display
  names on small videos
- The updating of the Component is defined in the parent class
  SmallVideo, which children will get access to through prototype
  copying
- Create a new actionType and middleware so name changes that occur
  in DisplayName can be propogated to outside redux
- Update the local video's DisplayName when a conference is joined
  or else the component may keep an undefined user id

* squash: query for the container, not the el owned by react
j8
virtuacoplenny 8 yıl önce
ebeveyn
işleme
928181cd7a

+ 1
- 6
conference.js Dosyayı Görüntüle

@@ -37,6 +37,7 @@ import {
37 37
 } from './react/features/base/lib-jitsi-meet';
38 38
 import {
39 39
     localParticipantRoleChanged,
40
+    MAX_DISPLAY_NAME_LENGTH,
40 41
     participantJoined,
41 42
     participantLeft,
42 43
     participantRoleChanged,
@@ -103,12 +104,6 @@ const commands = {
103 104
     SHARED_VIDEO: "shared-video"
104 105
 };
105 106
 
106
-/**
107
- * Max length of the display names. If we receive longer display name the
108
- * additional chars are going to be cut.
109
- */
110
-const MAX_DISPLAY_NAME_LENGTH = 50;
111
-
112 107
 /**
113 108
  * Open Connection. When authentication failed it shows auth dialog.
114 109
  * @param roomName the room name to use

+ 4
- 0
modules/UI/UI.js Dosyayı Görüntüle

@@ -253,6 +253,10 @@ UI.initConference = function () {
253 253
 
254 254
 UI.mucJoined = function () {
255 255
     VideoLayout.mucJoined();
256
+
257
+    // Update local video now that a conference is joined a user ID should be
258
+    // set.
259
+    UI.changeDisplayName('localVideoContainer', APP.settings.getDisplayName());
256 260
 };
257 261
 
258 262
 /***

+ 9
- 17
modules/UI/shared_video/SharedVideo.js Dosyayı Görüntüle

@@ -659,6 +659,10 @@ SharedVideoThumb.prototype.createContainer = function (spanId) {
659 659
     avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
660 660
     container.appendChild(avatar);
661 661
 
662
+    const displayNameContainer = document.createElement('div');
663
+    displayNameContainer.className = 'displayNameContainer';
664
+    container.appendChild(displayNameContainer);
665
+
662 666
     var remotes = document.getElementById('filmstripRemoteVideosContainer');
663 667
     return remotes.appendChild(container);
664 668
 };
@@ -696,23 +700,11 @@ SharedVideoThumb.prototype.setDisplayName = function(displayName) {
696 700
         return;
697 701
     }
698 702
 
699
-    var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
700
-
701
-    // If we already have a display name for this video.
702
-    if (nameSpan.length > 0) {
703
-        if (displayName && displayName.length > 0) {
704
-            $('#' + this.videoSpanId + '_name').text(displayName);
705
-        }
706
-    } else {
707
-        nameSpan = document.createElement('span');
708
-        nameSpan.className = 'displayname';
709
-        $('#' + this.videoSpanId)[0].appendChild(nameSpan);
710
-
711
-        if (displayName && displayName.length > 0)
712
-            $(nameSpan).text(displayName);
713
-        nameSpan.id = this.videoSpanId + '_name';
714
-    }
715
-
703
+    this.updateDisplayName({
704
+        displayName: displayName || '',
705
+        elementID: `${this.videoSpanId}_name`,
706
+        participantID: this.id
707
+    });
716 708
 };
717 709
 
718 710
 /**

+ 22
- 100
modules/UI/videolayout/LocalVideo.js Dosyayı Görüntüle

@@ -47,116 +47,38 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
47 47
         return;
48 48
     }
49 49
 
50
-    var nameSpan = $('#' + this.videoSpanId + ' .displayname');
51
-    var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
52
-        interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
53
-
54
-    var meHTML;
55
-    // If we already have a display name for this video.
56
-    if (nameSpan.length > 0) {
57
-        if (nameSpan.text() !== displayName) {
58
-            if (displayName && displayName.length > 0) {
59
-                meHTML = APP.translation.generateTranslationHTML("me");
60
-                $('#localDisplayName').html(
61
-                    `${UIUtil.escapeHtml(displayName)} (${meHTML})`
62
-                );
63
-                $('#editDisplayName').val(
64
-                    `${UIUtil.escapeHtml(displayName)}`
65
-                );
66
-            } else {
67
-                $('#localDisplayName').html(defaultLocalDisplayName);
68
-            }
69
-        }
70
-        this.updateView();
71
-    } else {
72
-        nameSpan = document.createElement('span');
73
-        nameSpan.className = 'displayname';
74
-        document.getElementById(this.videoSpanId)
75
-            .appendChild(nameSpan);
76
-
77
-
78
-        if (displayName && displayName.length > 0) {
79
-            meHTML = APP.translation.generateTranslationHTML("me");
80
-            nameSpan.innerHTML = UIUtil.escapeHtml(displayName) + meHTML;
81
-        }
82
-        else {
83
-            nameSpan.innerHTML = defaultLocalDisplayName;
84
-        }
85
-
86
-
87
-        nameSpan.id = 'localDisplayName';
88
-        //translates popover of edit button
89
-        APP.translation.translateElement($("a.displayname"));
90
-
91
-        var editableText = document.createElement('input');
92
-        editableText.className = 'editdisplayname';
93
-        editableText.type = 'text';
94
-        editableText.id = 'editDisplayName';
95
-
96
-        if (displayName && displayName.length) {
97
-            editableText.value = displayName;
98
-        }
99
-
100
-        editableText.setAttribute('style', 'display:none;');
101
-        editableText.setAttribute('data-i18n',
102
-            '[placeholder]defaultNickname');
103
-        editableText.setAttribute("data-i18n-options",
104
-            JSON.stringify({name: "Jane Pink"}));
105
-        APP.translation.translateElement($(editableText));
106
-
107
-        this.container
108
-            .appendChild(editableText);
109
-
110
-        var self = this;
111
-        $('#localVideoContainer .displayname')
112
-            .bind("click", function (e) {
113
-                let $editDisplayName = $('#editDisplayName');
114
-
115
-                e.preventDefault();
116
-                e.stopPropagation();
117
-                // we set display to be hidden
118
-                self.hideDisplayName = true;
119
-                // update the small video vide to hide the display name
120
-                self.updateView();
121
-                // disables further updates in the thumbnail to stay in the
122
-                // edit mode
123
-                self.disableUpdateView = true;
124
-
125
-                $editDisplayName.show();
126
-                $editDisplayName.focus();
127
-                $editDisplayName.select();
128
-
129
-                $editDisplayName.one("focusout", function () {
130
-                    self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
131
-                    $editDisplayName.hide();
132
-                    // stop editing, display displayName and resume updating
133
-                    // the thumbnail
134
-                    self.hideDisplayName = false;
135
-                    self.disableUpdateView = false;
136
-                    self.updateView();
137
-                });
138
-
139
-                $editDisplayName.on('keydown', function (e) {
140
-                    if (e.keyCode === 13) {
141
-                        e.preventDefault();
142
-                        $('#editDisplayName').hide();
143
-                        // focusout handler will save display name
144
-                    }
145
-                });
146
-            });
147
-    }
50
+    this.updateDisplayName({
51
+        allowEditing: true,
52
+        displayName: displayName,
53
+        displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
54
+        elementID: 'localDisplayName',
55
+        participantID: this.id
56
+    });
148 57
 };
149 58
 
150 59
 LocalVideo.prototype.changeVideo = function (stream) {
151 60
     this.videoStream = stream;
152 61
 
153 62
     let localVideoClick = (event) => {
63
+        // TODO Checking the classList is a workround to allow events to bubble
64
+        // into the DisplayName component if it was clicked. React's synthetic
65
+        // events will fire after jQuery handlers execute, so stop propogation
66
+        // at this point will prevent DisplayName from getting click events.
67
+        // This workaround should be removeable once LocalVideo is a React
68
+        // Component because then the components share the same eventing system.
69
+        const { classList } = event.target;
70
+        const clickedOnDisplayName = classList.contains('displayname')
71
+            || classList.contains('editdisplayname');
72
+
154 73
         // FIXME: with Temasys plugin event arg is not an event, but
155 74
         // the clicked object itself, so we have to skip this call
156
-        if (event.stopPropagation) {
75
+        if (event.stopPropagation && !clickedOnDisplayName) {
157 76
             event.stopPropagation();
158 77
         }
159
-        this.VideoLayout.handleVideoThumbClicked(this.id);
78
+
79
+        if (!clickedOnDisplayName) {
80
+            this.VideoLayout.handleVideoThumbClicked(this.id);
81
+        }
160 82
     };
161 83
 
162 84
     let localVideoContainerSelector = $('#localVideoContainer');

+ 10
- 25
modules/UI/videolayout/RemoteVideo.js Dosyayı Görüntüle

@@ -516,6 +516,7 @@ RemoteVideo.prototype.remove = function () {
516 516
     this.removeAudioLevelIndicator();
517 517
 
518 518
     this.removeConnectionIndicator();
519
+    this.removeDisplayName();
519 520
     // Make sure that the large video is updated if are removing its
520 521
     // corresponding small video.
521 522
     this.VideoLayout.updateAfterThumbRemoved(this.id);
@@ -640,31 +641,11 @@ RemoteVideo.prototype.setDisplayName = function(displayName) {
640 641
         return;
641 642
     }
642 643
 
643
-    var nameSpan = $('#' + this.videoSpanId + ' .displayname');
644
-
645
-    // If we already have a display name for this video.
646
-    if (nameSpan.length > 0) {
647
-        if (displayName && displayName.length > 0) {
648
-            var displaynameSpan = $('#' + this.videoSpanId + '_name');
649
-            if (displaynameSpan.text() !== displayName)
650
-                displaynameSpan.text(displayName);
651
-        }
652
-        else
653
-            $('#' + this.videoSpanId + '_name').text(
654
-                interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
655
-    } else {
656
-        nameSpan = document.createElement('span');
657
-        nameSpan.className = 'displayname';
658
-        $('#' + this.videoSpanId)[0]
659
-            .appendChild(nameSpan);
660
-
661
-        if (displayName && displayName.length > 0) {
662
-            $(nameSpan).text(displayName);
663
-        } else {
664
-            nameSpan.innerHTML = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
665
-        }
666
-        nameSpan.id = this.videoSpanId + '_name';
667
-    }
644
+    this.updateDisplayName({
645
+        displayName: displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME,
646
+        elementID: `${this.videoSpanId}_name`,
647
+        participantID: this.id
648
+    });
668 649
 };
669 650
 
670 651
 /**
@@ -707,6 +688,10 @@ RemoteVideo.createContainer = function (spanId) {
707 688
     overlay.className = "videocontainer__hoverOverlay";
708 689
     container.appendChild(overlay);
709 690
 
691
+    const displayNameContainer = document.createElement('div');
692
+    displayNameContainer.className = 'displayNameContainer';
693
+    container.appendChild(displayNameContainer);
694
+
710 695
     var remotes = document.getElementById('filmstripRemoteVideosContainer');
711 696
     return remotes.appendChild(container);
712 697
 };

+ 44
- 2
modules/UI/videolayout/SmallVideo.js Dosyayı Görüntüle

@@ -3,13 +3,17 @@
3 3
 /* eslint-disable no-unused-vars */
4 4
 import React from 'react';
5 5
 import ReactDOM from 'react-dom';
6
+import { I18nextProvider } from 'react-i18next';
7
+import { Provider } from 'react-redux';
6 8
 
9
+import { i18next } from '../../../react/features/base/i18n';
7 10
 import { AudioLevelIndicator }
8 11
     from '../../../react/features/audio-level-indicator';
9 12
 import {
10 13
     ConnectionIndicator
11 14
 } from '../../../react/features/connection-indicator';
12
-/* eslint-enable no-unused-vars */
15
+import { DisplayName } from '../../../react/features/display-name';
16
+/* eslint-disable no-unused-vars */
13 17
 
14 18
 const logger = require("jitsi-meet-logger").getLogger(__filename);
15 19
 
@@ -483,7 +487,45 @@ SmallVideo.prototype.$avatar = function () {
483 487
  * the video thumbnail
484 488
  */
485 489
 SmallVideo.prototype.$displayName = function () {
486
-    return $('#' + this.videoSpanId + ' .displayname');
490
+    return $('#' + this.videoSpanId + ' .displayNameContainer');
491
+};
492
+
493
+/**
494
+ * Creates or updates the participant's display name that is shown over the
495
+ * video preview.
496
+ *
497
+ * @returns {void}
498
+ */
499
+SmallVideo.prototype.updateDisplayName = function (props) {
500
+    const displayNameContainer
501
+        = this.container.querySelector('.displayNameContainer');
502
+
503
+    if (displayNameContainer) {
504
+        /* jshint ignore:start */
505
+        ReactDOM.render(
506
+            <Provider store = { APP.store }>
507
+                <I18nextProvider i18n = { i18next }>
508
+                    <DisplayName { ...props } />
509
+                </I18nextProvider>
510
+            </Provider>,
511
+            displayNameContainer);
512
+        /* jshint ignore:end */
513
+    }
514
+};
515
+
516
+/**
517
+ * Removes the component responsible for showing the participant's display name,
518
+ * if its container is present.
519
+ *
520
+ * @returns {void}
521
+ */
522
+SmallVideo.prototype.removeDisplayName = function () {
523
+    const displayNameContainer
524
+        = this.container.querySelector('.displayNameContainer');
525
+
526
+    if (displayNameContainer) {
527
+        ReactDOM.unmountComponentAtNode(displayNameContainer);
528
+    }
487 529
 };
488 530
 
489 531
 /**

+ 12
- 0
react/features/base/participants/actionTypes.js Dosyayı Görüntüle

@@ -10,6 +10,18 @@
10 10
  */
11 11
 export const DOMINANT_SPEAKER_CHANGED = Symbol('DOMINANT_SPEAKER_CHANGED');
12 12
 
13
+/**
14
+ * Create an action for when the local participant's display name is updated.
15
+ *
16
+ * {
17
+ *     type: PARTICIPANT_DISPLAY_NAME_CHANGED,
18
+ *     id: string,
19
+ *     name: string
20
+ * }
21
+ */
22
+export const PARTICIPANT_DISPLAY_NAME_CHANGED
23
+    = Symbol('PARTICIPANT_DISPLAY_NAME_CHANGED');
24
+
13 25
 /**
14 26
  * Action to signal that ID of participant has changed. This happens when
15 27
  * local participant joins a new conference or quits one.

+ 25
- 0
react/features/base/participants/actions.js Dosyayı Görüntüle

@@ -1,11 +1,13 @@
1 1
 import {
2 2
     DOMINANT_SPEAKER_CHANGED,
3
+    PARTICIPANT_DISPLAY_NAME_CHANGED,
3 4
     PARTICIPANT_ID_CHANGED,
4 5
     PARTICIPANT_JOINED,
5 6
     PARTICIPANT_LEFT,
6 7
     PARTICIPANT_UPDATED,
7 8
     PIN_PARTICIPANT
8 9
 } from './actionTypes';
10
+import { MAX_DISPLAY_NAME_LENGTH } from './constants';
9 11
 import { getLocalParticipant } from './functions';
10 12
 
11 13
 /**
@@ -123,6 +125,29 @@ export function localParticipantLeft() {
123 125
     };
124 126
 }
125 127
 
128
+/**
129
+ * Action to signal that a participant's display name has changed.
130
+ *
131
+ * @param {string} id - The id of the participant being changed.
132
+ * @param {string} displayName - The new display name.
133
+ * @returns {{
134
+ *     type: PARTICIPANT_DISPLAY_NAME_CHANGED,
135
+ *     id: string,
136
+ *     name: string
137
+ * }}
138
+ */
139
+export function participantDisplayNameChanged(id, displayName = '') {
140
+    // FIXME Do not use this action over participantUpdated. This action exists
141
+    // as a a bridge for local name updates. Once other components responsible
142
+    // for updating the local user's display name are in react/redux, this
143
+    // action should be replaceable with the participantUpdated action.
144
+    return {
145
+        type: PARTICIPANT_DISPLAY_NAME_CHANGED,
146
+        id,
147
+        name: displayName.substr(0, MAX_DISPLAY_NAME_LENGTH)
148
+    };
149
+}
150
+
126 151
 /**
127 152
  * Action to signal that a participant has joined.
128 153
  *

+ 7
- 0
react/features/base/participants/constants.js Dosyayı Görüntüle

@@ -6,6 +6,13 @@
6 6
  */
7 7
 export const LOCAL_PARTICIPANT_DEFAULT_ID = 'local';
8 8
 
9
+/**
10
+ * Max length of the display names.
11
+ *
12
+ * @type {string}
13
+ */
14
+export const MAX_DISPLAY_NAME_LENGTH = 50;
15
+
9 16
 /**
10 17
  * The set of possible XMPP MUC roles for conference participants.
11 18
  *

+ 20
- 0
react/features/base/participants/middleware.js Dosyayı Görüntüle

@@ -1,3 +1,5 @@
1
+import UIEvents from '../../../../service/UI/UIEvents';
2
+
1 3
 import {
2 4
     CONFERENCE_JOINED,
3 5
     CONFERENCE_LEFT
@@ -5,7 +7,11 @@ import {
5 7
 import { MiddlewareRegistry } from '../redux';
6 8
 
7 9
 import { localParticipantIdChanged } from './actions';
10
+import { PARTICIPANT_DISPLAY_NAME_CHANGED } from './actionTypes';
8 11
 import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
12
+import { getLocalParticipant } from './functions';
13
+
14
+declare var APP: Object;
9 15
 
10 16
 /**
11 17
  * Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
@@ -23,6 +29,20 @@ MiddlewareRegistry.register(store => next => action => {
23 29
     case CONFERENCE_LEFT:
24 30
         store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
25 31
         break;
32
+
33
+    // TODO Remove this middleware when the local display name update flow is
34
+    // fully brought into redux.
35
+    case PARTICIPANT_DISPLAY_NAME_CHANGED: {
36
+        if (typeof APP !== 'undefined') {
37
+            const participant = getLocalParticipant(store.getState());
38
+
39
+            if (participant && participant.id === action.id) {
40
+                APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
41
+            }
42
+        }
43
+
44
+        break;
45
+    }
26 46
     }
27 47
 
28 48
     return next(action);

+ 239
- 0
react/features/display-name/components/DisplayName.js Dosyayı Görüntüle

@@ -0,0 +1,239 @@
1
+import React, { Component } from 'react';
2
+import { connect } from 'react-redux';
3
+
4
+import { translate } from '../../base/i18n';
5
+import { participantDisplayNameChanged } from '../../base/participants';
6
+
7
+/**
8
+ * React {@code Component} for displaying and editing a participant's name.
9
+ *
10
+ * @extends Component
11
+ */
12
+class DisplayName extends Component {
13
+    /**
14
+     * {@code DisplayName}'s property types.
15
+     *
16
+     * @static
17
+     */
18
+    static propTypes = {
19
+        /**
20
+         * Whether or not the display name should be editable on click.
21
+         */
22
+        allowEditing: React.PropTypes.bool,
23
+
24
+        /**
25
+         * Invoked to update the participant's display name.
26
+         */
27
+        dispatch: React.PropTypes.func,
28
+
29
+        /**
30
+         * The participant's current display name.
31
+         */
32
+        displayName: React.PropTypes.string,
33
+
34
+        /**
35
+         * A string to append to the displayName, if provided.
36
+         */
37
+        displayNameSuffix: React.PropTypes.string,
38
+
39
+        /**
40
+         * The ID attribute to add to the component. Useful for global querying
41
+         * for the component by legacy components and torture tests.
42
+         */
43
+        elementID: React.PropTypes.string,
44
+
45
+        /**
46
+         * The ID of the participant whose name is being displayed.
47
+         */
48
+        participantID: React.PropTypes.string,
49
+
50
+        /**
51
+         * Invoked to obtain translated strings.
52
+         */
53
+        t: React.PropTypes.func
54
+    };
55
+
56
+    /**
57
+     * Initializes a new {@code DisplayName} instance.
58
+     *
59
+     * @param {Object} props - The read-only properties with which the new
60
+     * instance is to be initialized.
61
+     */
62
+    constructor(props) {
63
+        super(props);
64
+
65
+        this.state = {
66
+            /**
67
+             * The current value of the display name in the edit field.
68
+             *
69
+             * @type {string}
70
+             */
71
+            editDisplayNameValue: '',
72
+
73
+            /**
74
+             * Whether or not the component should be displaying an editable
75
+             * input.
76
+             *
77
+             * @type {boolean}
78
+             */
79
+            isEditing: false
80
+        };
81
+
82
+        /**
83
+         * The internal reference to the HTML element backing the React
84
+         * {@code Component} input with id {@code editDisplayName}. It is
85
+         * necessary for automatically selecting the display name input field
86
+         * when starting to edit the display name.
87
+         *
88
+         * @private
89
+         * @type {HTMLInputElement}
90
+         */
91
+        this._nameInput = null;
92
+
93
+        // Bind event handlers so they are only bound once for every instance.
94
+        this._onChange = this._onChange.bind(this);
95
+        this._onKeyDown = this._onKeyDown.bind(this);
96
+        this._onStartEditing = this._onStartEditing.bind(this);
97
+        this._onSubmit = this._onSubmit.bind(this);
98
+        this._setNameInputRef = this._setNameInputRef.bind(this);
99
+    }
100
+
101
+    /**
102
+     * Automatically selects the input field's value after starting to edit the
103
+     * display name.
104
+     *
105
+     * @inheritdoc
106
+     * @returns {void}
107
+     */
108
+    componentDidUpdate(previousProps, previousState) {
109
+        if (!previousState.isEditing && this.state.isEditing) {
110
+            this._nameInput.select();
111
+        }
112
+    }
113
+
114
+    /**
115
+     * Implements React's {@link Component#render()}.
116
+     *
117
+     * @inheritdoc
118
+     * @returns {ReactElement}
119
+     */
120
+    render() {
121
+        const {
122
+            allowEditing,
123
+            displayName,
124
+            displayNameSuffix,
125
+            elementID,
126
+            t
127
+        } = this.props;
128
+
129
+        if (allowEditing && this.state.isEditing) {
130
+            return (
131
+                <input
132
+                    autoFocus = { true }
133
+                    className = 'editdisplayname'
134
+                    id = 'editDisplayName'
135
+                    onBlur = { this._onSubmit }
136
+                    onChange = { this._onChange }
137
+                    onKeyDown = { this._onKeyDown }
138
+                    placeholder = { t('defaultNickname') }
139
+                    ref = { this._setNameInputRef }
140
+                    type = 'text'
141
+                    value = { this.state.editDisplayNameValue } />
142
+            );
143
+        }
144
+
145
+        const suffix
146
+            = displayName && displayNameSuffix ? ` (${displayNameSuffix})` : '';
147
+
148
+        return (
149
+            <span
150
+                className = 'displayname'
151
+                id = { elementID }
152
+                onClick = { this._onStartEditing }>
153
+                { `${displayName || displayNameSuffix || ''}${suffix}` }
154
+            </span>
155
+        );
156
+    }
157
+
158
+    /**
159
+     * Updates the internal state of the display name entered into the edit
160
+     * field.
161
+     *
162
+     * @param {Object} event - DOM Event for value change.
163
+     * @private
164
+     * @returns {void}
165
+     */
166
+    _onChange(event) {
167
+        this.setState({
168
+            editDisplayNameValue: event.target.value
169
+        });
170
+    }
171
+
172
+    /**
173
+     * Submits the editted display name update if the enter key is pressed.
174
+     *
175
+     * @param {Event} event - Key down event object.
176
+     * @private
177
+     * @returns {void}
178
+     */
179
+    _onKeyDown(event) {
180
+        if (event.key === 'Enter') {
181
+            this._onSubmit();
182
+        }
183
+    }
184
+
185
+    /**
186
+     * Updates the component to display an editable input field and sets the
187
+     * initial value to the current display name.
188
+     *
189
+     * @private
190
+     * @returns {void}
191
+     */
192
+    _onStartEditing() {
193
+        if (this.props.allowEditing) {
194
+            this.setState({
195
+                isEditing: true,
196
+                editDisplayNameValue: this.props.displayName || ''
197
+            });
198
+        }
199
+    }
200
+
201
+    /**
202
+     * Dispatches an action to update the display name if any change has
203
+     * occurred after editing. Clears any temporary state used to keep track
204
+     * of pending display name changes and exits editing mode.
205
+     *
206
+     * @param {Event} event - Key down event object.
207
+     * @private
208
+     * @returns {void}
209
+     */
210
+    _onSubmit() {
211
+        const { editDisplayNameValue } = this.state;
212
+        const { dispatch, participantID } = this.props;
213
+
214
+        dispatch(participantDisplayNameChanged(
215
+            participantID, editDisplayNameValue));
216
+
217
+        this.setState({
218
+            isEditing: false,
219
+            editDisplayNameValue: ''
220
+        });
221
+
222
+        this._nameInput = null;
223
+    }
224
+
225
+    /**
226
+     * Sets the internal reference to the HTML element backing the React
227
+     * {@code Component} input with id {@code editDisplayName}.
228
+     *
229
+     * @param {HTMLInputElement} element - The DOM/HTML element for this
230
+     * {@code Component}'s input.
231
+     * @private
232
+     * @returns {void}
233
+     */
234
+    _setNameInputRef(element) {
235
+        this._nameInput = element;
236
+    }
237
+}
238
+
239
+export default translate(connect()(DisplayName));

+ 1
- 0
react/features/display-name/components/index.js Dosyayı Görüntüle

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

+ 1
- 0
react/features/display-name/index.js Dosyayı Görüntüle

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

+ 1
- 0
react/features/filmstrip/components/Filmstrip.web.js Dosyayı Görüntüle

@@ -40,6 +40,7 @@ export default class Filmstrip extends Component {
40 40
                                         = 'connection-indicator-container' />
41 41
                             </div>
42 42
                             <div className = 'videocontainer__hoverOverlay' />
43
+                            <div className = 'displayNameContainer' />
43 44
                         </span>
44 45
                     </div>
45 46
                     <div

Loading…
İptal
Kaydet