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
This commit is contained in:
parent
e7a4318e8c
commit
928181cd7a
|
@ -37,6 +37,7 @@ import {
|
|||
} from './react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
localParticipantRoleChanged,
|
||||
MAX_DISPLAY_NAME_LENGTH,
|
||||
participantJoined,
|
||||
participantLeft,
|
||||
participantRoleChanged,
|
||||
|
@ -103,12 +104,6 @@ const commands = {
|
|||
SHARED_VIDEO: "shared-video"
|
||||
};
|
||||
|
||||
/**
|
||||
* Max length of the display names. If we receive longer display name the
|
||||
* additional chars are going to be cut.
|
||||
*/
|
||||
const MAX_DISPLAY_NAME_LENGTH = 50;
|
||||
|
||||
/**
|
||||
* Open Connection. When authentication failed it shows auth dialog.
|
||||
* @param roomName the room name to use
|
||||
|
|
|
@ -253,6 +253,10 @@ UI.initConference = function () {
|
|||
|
||||
UI.mucJoined = function () {
|
||||
VideoLayout.mucJoined();
|
||||
|
||||
// Update local video now that a conference is joined a user ID should be
|
||||
// set.
|
||||
UI.changeDisplayName('localVideoContainer', APP.settings.getDisplayName());
|
||||
};
|
||||
|
||||
/***
|
||||
|
|
|
@ -659,6 +659,10 @@ SharedVideoThumb.prototype.createContainer = function (spanId) {
|
|||
avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
|
||||
container.appendChild(avatar);
|
||||
|
||||
const displayNameContainer = document.createElement('div');
|
||||
displayNameContainer.className = 'displayNameContainer';
|
||||
container.appendChild(displayNameContainer);
|
||||
|
||||
var remotes = document.getElementById('filmstripRemoteVideosContainer');
|
||||
return remotes.appendChild(container);
|
||||
};
|
||||
|
@ -696,23 +700,11 @@ SharedVideoThumb.prototype.setDisplayName = function(displayName) {
|
|||
return;
|
||||
}
|
||||
|
||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
||||
|
||||
// If we already have a display name for this video.
|
||||
if (nameSpan.length > 0) {
|
||||
if (displayName && displayName.length > 0) {
|
||||
$('#' + this.videoSpanId + '_name').text(displayName);
|
||||
}
|
||||
} else {
|
||||
nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'displayname';
|
||||
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
|
||||
|
||||
if (displayName && displayName.length > 0)
|
||||
$(nameSpan).text(displayName);
|
||||
nameSpan.id = this.videoSpanId + '_name';
|
||||
}
|
||||
|
||||
this.updateDisplayName({
|
||||
displayName: displayName || '',
|
||||
elementID: `${this.videoSpanId}_name`,
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,116 +47,38 @@ LocalVideo.prototype.setDisplayName = function(displayName) {
|
|||
return;
|
||||
}
|
||||
|
||||
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
|
||||
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||
|
||||
var meHTML;
|
||||
// If we already have a display name for this video.
|
||||
if (nameSpan.length > 0) {
|
||||
if (nameSpan.text() !== displayName) {
|
||||
if (displayName && displayName.length > 0) {
|
||||
meHTML = APP.translation.generateTranslationHTML("me");
|
||||
$('#localDisplayName').html(
|
||||
`${UIUtil.escapeHtml(displayName)} (${meHTML})`
|
||||
);
|
||||
$('#editDisplayName').val(
|
||||
`${UIUtil.escapeHtml(displayName)}`
|
||||
);
|
||||
} else {
|
||||
$('#localDisplayName').html(defaultLocalDisplayName);
|
||||
}
|
||||
}
|
||||
this.updateView();
|
||||
} else {
|
||||
nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'displayname';
|
||||
document.getElementById(this.videoSpanId)
|
||||
.appendChild(nameSpan);
|
||||
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
meHTML = APP.translation.generateTranslationHTML("me");
|
||||
nameSpan.innerHTML = UIUtil.escapeHtml(displayName) + meHTML;
|
||||
}
|
||||
else {
|
||||
nameSpan.innerHTML = defaultLocalDisplayName;
|
||||
}
|
||||
|
||||
|
||||
nameSpan.id = 'localDisplayName';
|
||||
//translates popover of edit button
|
||||
APP.translation.translateElement($("a.displayname"));
|
||||
|
||||
var editableText = document.createElement('input');
|
||||
editableText.className = 'editdisplayname';
|
||||
editableText.type = 'text';
|
||||
editableText.id = 'editDisplayName';
|
||||
|
||||
if (displayName && displayName.length) {
|
||||
editableText.value = displayName;
|
||||
}
|
||||
|
||||
editableText.setAttribute('style', 'display:none;');
|
||||
editableText.setAttribute('data-i18n',
|
||||
'[placeholder]defaultNickname');
|
||||
editableText.setAttribute("data-i18n-options",
|
||||
JSON.stringify({name: "Jane Pink"}));
|
||||
APP.translation.translateElement($(editableText));
|
||||
|
||||
this.container
|
||||
.appendChild(editableText);
|
||||
|
||||
var self = this;
|
||||
$('#localVideoContainer .displayname')
|
||||
.bind("click", function (e) {
|
||||
let $editDisplayName = $('#editDisplayName');
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// we set display to be hidden
|
||||
self.hideDisplayName = true;
|
||||
// update the small video vide to hide the display name
|
||||
self.updateView();
|
||||
// disables further updates in the thumbnail to stay in the
|
||||
// edit mode
|
||||
self.disableUpdateView = true;
|
||||
|
||||
$editDisplayName.show();
|
||||
$editDisplayName.focus();
|
||||
$editDisplayName.select();
|
||||
|
||||
$editDisplayName.one("focusout", function () {
|
||||
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
|
||||
$editDisplayName.hide();
|
||||
// stop editing, display displayName and resume updating
|
||||
// the thumbnail
|
||||
self.hideDisplayName = false;
|
||||
self.disableUpdateView = false;
|
||||
self.updateView();
|
||||
});
|
||||
|
||||
$editDisplayName.on('keydown', function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
$('#editDisplayName').hide();
|
||||
// focusout handler will save display name
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
this.updateDisplayName({
|
||||
allowEditing: true,
|
||||
displayName: displayName,
|
||||
displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
||||
elementID: 'localDisplayName',
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
|
||||
LocalVideo.prototype.changeVideo = function (stream) {
|
||||
this.videoStream = stream;
|
||||
|
||||
let localVideoClick = (event) => {
|
||||
// TODO Checking the classList is a workround to allow events to bubble
|
||||
// into the DisplayName component if it was clicked. React's synthetic
|
||||
// events will fire after jQuery handlers execute, so stop propogation
|
||||
// at this point will prevent DisplayName from getting click events.
|
||||
// This workaround should be removeable once LocalVideo is a React
|
||||
// Component because then the components share the same eventing system.
|
||||
const { classList } = event.target;
|
||||
const clickedOnDisplayName = classList.contains('displayname')
|
||||
|| classList.contains('editdisplayname');
|
||||
|
||||
// FIXME: with Temasys plugin event arg is not an event, but
|
||||
// the clicked object itself, so we have to skip this call
|
||||
if (event.stopPropagation) {
|
||||
if (event.stopPropagation && !clickedOnDisplayName) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.VideoLayout.handleVideoThumbClicked(this.id);
|
||||
|
||||
if (!clickedOnDisplayName) {
|
||||
this.VideoLayout.handleVideoThumbClicked(this.id);
|
||||
}
|
||||
};
|
||||
|
||||
let localVideoContainerSelector = $('#localVideoContainer');
|
||||
|
|
|
@ -516,6 +516,7 @@ RemoteVideo.prototype.remove = function () {
|
|||
this.removeAudioLevelIndicator();
|
||||
|
||||
this.removeConnectionIndicator();
|
||||
this.removeDisplayName();
|
||||
// Make sure that the large video is updated if are removing its
|
||||
// corresponding small video.
|
||||
this.VideoLayout.updateAfterThumbRemoved(this.id);
|
||||
|
@ -640,31 +641,11 @@ RemoteVideo.prototype.setDisplayName = function(displayName) {
|
|||
return;
|
||||
}
|
||||
|
||||
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
|
||||
|
||||
// If we already have a display name for this video.
|
||||
if (nameSpan.length > 0) {
|
||||
if (displayName && displayName.length > 0) {
|
||||
var displaynameSpan = $('#' + this.videoSpanId + '_name');
|
||||
if (displaynameSpan.text() !== displayName)
|
||||
displaynameSpan.text(displayName);
|
||||
}
|
||||
else
|
||||
$('#' + this.videoSpanId + '_name').text(
|
||||
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
|
||||
} else {
|
||||
nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'displayname';
|
||||
$('#' + this.videoSpanId)[0]
|
||||
.appendChild(nameSpan);
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
$(nameSpan).text(displayName);
|
||||
} else {
|
||||
nameSpan.innerHTML = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
}
|
||||
nameSpan.id = this.videoSpanId + '_name';
|
||||
}
|
||||
this.updateDisplayName({
|
||||
displayName: displayName || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME,
|
||||
elementID: `${this.videoSpanId}_name`,
|
||||
participantID: this.id
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -707,6 +688,10 @@ RemoteVideo.createContainer = function (spanId) {
|
|||
overlay.className = "videocontainer__hoverOverlay";
|
||||
container.appendChild(overlay);
|
||||
|
||||
const displayNameContainer = document.createElement('div');
|
||||
displayNameContainer.className = 'displayNameContainer';
|
||||
container.appendChild(displayNameContainer);
|
||||
|
||||
var remotes = document.getElementById('filmstripRemoteVideosContainer');
|
||||
return remotes.appendChild(container);
|
||||
};
|
||||
|
|
|
@ -3,13 +3,17 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { i18next } from '../../../react/features/base/i18n';
|
||||
import { AudioLevelIndicator }
|
||||
from '../../../react/features/audio-level-indicator';
|
||||
import {
|
||||
ConnectionIndicator
|
||||
} from '../../../react/features/connection-indicator';
|
||||
/* eslint-enable no-unused-vars */
|
||||
import { DisplayName } from '../../../react/features/display-name';
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||
|
||||
|
@ -483,7 +487,45 @@ SmallVideo.prototype.$avatar = function () {
|
|||
* the video thumbnail
|
||||
*/
|
||||
SmallVideo.prototype.$displayName = function () {
|
||||
return $('#' + this.videoSpanId + ' .displayname');
|
||||
return $('#' + this.videoSpanId + ' .displayNameContainer');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates or updates the participant's display name that is shown over the
|
||||
* video preview.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.updateDisplayName = function (props) {
|
||||
const displayNameContainer
|
||||
= this.container.querySelector('.displayNameContainer');
|
||||
|
||||
if (displayNameContainer) {
|
||||
/* jshint ignore:start */
|
||||
ReactDOM.render(
|
||||
<Provider store = { APP.store }>
|
||||
<I18nextProvider i18n = { i18next }>
|
||||
<DisplayName { ...props } />
|
||||
</I18nextProvider>
|
||||
</Provider>,
|
||||
displayNameContainer);
|
||||
/* jshint ignore:end */
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the component responsible for showing the participant's display name,
|
||||
* if its container is present.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
SmallVideo.prototype.removeDisplayName = function () {
|
||||
const displayNameContainer
|
||||
= this.container.querySelector('.displayNameContainer');
|
||||
|
||||
if (displayNameContainer) {
|
||||
ReactDOM.unmountComponentAtNode(displayNameContainer);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -10,6 +10,18 @@
|
|||
*/
|
||||
export const DOMINANT_SPEAKER_CHANGED = Symbol('DOMINANT_SPEAKER_CHANGED');
|
||||
|
||||
/**
|
||||
* Create an action for when the local participant's display name is updated.
|
||||
*
|
||||
* {
|
||||
* type: PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
* id: string,
|
||||
* name: string
|
||||
* }
|
||||
*/
|
||||
export const PARTICIPANT_DISPLAY_NAME_CHANGED
|
||||
= Symbol('PARTICIPANT_DISPLAY_NAME_CHANGED');
|
||||
|
||||
/**
|
||||
* Action to signal that ID of participant has changed. This happens when
|
||||
* local participant joins a new conference or quits one.
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
DOMINANT_SPEAKER_CHANGED,
|
||||
PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
PARTICIPANT_ID_CHANGED,
|
||||
PARTICIPANT_JOINED,
|
||||
PARTICIPANT_LEFT,
|
||||
PARTICIPANT_UPDATED,
|
||||
PIN_PARTICIPANT
|
||||
} from './actionTypes';
|
||||
import { MAX_DISPLAY_NAME_LENGTH } from './constants';
|
||||
import { getLocalParticipant } from './functions';
|
||||
|
||||
/**
|
||||
|
@ -123,6 +125,29 @@ export function localParticipantLeft() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that a participant's display name has changed.
|
||||
*
|
||||
* @param {string} id - The id of the participant being changed.
|
||||
* @param {string} displayName - The new display name.
|
||||
* @returns {{
|
||||
* type: PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
* id: string,
|
||||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export function participantDisplayNameChanged(id, displayName = '') {
|
||||
// FIXME Do not use this action over participantUpdated. This action exists
|
||||
// as a a bridge for local name updates. Once other components responsible
|
||||
// for updating the local user's display name are in react/redux, this
|
||||
// action should be replaceable with the participantUpdated action.
|
||||
return {
|
||||
type: PARTICIPANT_DISPLAY_NAME_CHANGED,
|
||||
id,
|
||||
name: displayName.substr(0, MAX_DISPLAY_NAME_LENGTH)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to signal that a participant has joined.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
*/
|
||||
export const LOCAL_PARTICIPANT_DEFAULT_ID = 'local';
|
||||
|
||||
/**
|
||||
* Max length of the display names.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const MAX_DISPLAY_NAME_LENGTH = 50;
|
||||
|
||||
/**
|
||||
* The set of possible XMPP MUC roles for conference participants.
|
||||
*
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_LEFT
|
||||
|
@ -5,7 +7,11 @@ import {
|
|||
import { MiddlewareRegistry } from '../redux';
|
||||
|
||||
import { localParticipantIdChanged } from './actions';
|
||||
import { PARTICIPANT_DISPLAY_NAME_CHANGED } from './actionTypes';
|
||||
import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
|
||||
import { getLocalParticipant } from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
|
||||
|
@ -23,6 +29,20 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
case CONFERENCE_LEFT:
|
||||
store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
|
||||
break;
|
||||
|
||||
// TODO Remove this middleware when the local display name update flow is
|
||||
// fully brought into redux.
|
||||
case PARTICIPANT_DISPLAY_NAME_CHANGED: {
|
||||
if (typeof APP !== 'undefined') {
|
||||
const participant = getLocalParticipant(store.getState());
|
||||
|
||||
if (participant && participant.id === action.id) {
|
||||
APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { participantDisplayNameChanged } from '../../base/participants';
|
||||
|
||||
/**
|
||||
* React {@code Component} for displaying and editing a participant's name.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
class DisplayName extends Component {
|
||||
/**
|
||||
* {@code DisplayName}'s property types.
|
||||
*
|
||||
* @static
|
||||
*/
|
||||
static propTypes = {
|
||||
/**
|
||||
* Whether or not the display name should be editable on click.
|
||||
*/
|
||||
allowEditing: React.PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Invoked to update the participant's display name.
|
||||
*/
|
||||
dispatch: React.PropTypes.func,
|
||||
|
||||
/**
|
||||
* The participant's current display name.
|
||||
*/
|
||||
displayName: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* A string to append to the displayName, if provided.
|
||||
*/
|
||||
displayNameSuffix: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The ID attribute to add to the component. Useful for global querying
|
||||
* for the component by legacy components and torture tests.
|
||||
*/
|
||||
elementID: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* The ID of the participant whose name is being displayed.
|
||||
*/
|
||||
participantID: React.PropTypes.string,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: React.PropTypes.func
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a new {@code DisplayName} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
/**
|
||||
* The current value of the display name in the edit field.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
editDisplayNameValue: '',
|
||||
|
||||
/**
|
||||
* Whether or not the component should be displaying an editable
|
||||
* input.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
isEditing: false
|
||||
};
|
||||
|
||||
/**
|
||||
* The internal reference to the HTML element backing the React
|
||||
* {@code Component} input with id {@code editDisplayName}. It is
|
||||
* necessary for automatically selecting the display name input field
|
||||
* when starting to edit the display name.
|
||||
*
|
||||
* @private
|
||||
* @type {HTMLInputElement}
|
||||
*/
|
||||
this._nameInput = null;
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onStartEditing = this._onStartEditing.bind(this);
|
||||
this._onSubmit = this._onSubmit.bind(this);
|
||||
this._setNameInputRef = this._setNameInputRef.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically selects the input field's value after starting to edit the
|
||||
* display name.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {void}
|
||||
*/
|
||||
componentDidUpdate(previousProps, previousState) {
|
||||
if (!previousState.isEditing && this.state.isEditing) {
|
||||
this._nameInput.select();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const {
|
||||
allowEditing,
|
||||
displayName,
|
||||
displayNameSuffix,
|
||||
elementID,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
if (allowEditing && this.state.isEditing) {
|
||||
return (
|
||||
<input
|
||||
autoFocus = { true }
|
||||
className = 'editdisplayname'
|
||||
id = 'editDisplayName'
|
||||
onBlur = { this._onSubmit }
|
||||
onChange = { this._onChange }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
placeholder = { t('defaultNickname') }
|
||||
ref = { this._setNameInputRef }
|
||||
type = 'text'
|
||||
value = { this.state.editDisplayNameValue } />
|
||||
);
|
||||
}
|
||||
|
||||
const suffix
|
||||
= displayName && displayNameSuffix ? ` (${displayNameSuffix})` : '';
|
||||
|
||||
return (
|
||||
<span
|
||||
className = 'displayname'
|
||||
id = { elementID }
|
||||
onClick = { this._onStartEditing }>
|
||||
{ `${displayName || displayNameSuffix || ''}${suffix}` }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal state of the display name entered into the edit
|
||||
* field.
|
||||
*
|
||||
* @param {Object} event - DOM Event for value change.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChange(event) {
|
||||
this.setState({
|
||||
editDisplayNameValue: event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the editted display name update if the enter key is pressed.
|
||||
*
|
||||
* @param {Event} event - Key down event object.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
this._onSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the component to display an editable input field and sets the
|
||||
* initial value to the current display name.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartEditing() {
|
||||
if (this.props.allowEditing) {
|
||||
this.setState({
|
||||
isEditing: true,
|
||||
editDisplayNameValue: this.props.displayName || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an action to update the display name if any change has
|
||||
* occurred after editing. Clears any temporary state used to keep track
|
||||
* of pending display name changes and exits editing mode.
|
||||
*
|
||||
* @param {Event} event - Key down event object.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { editDisplayNameValue } = this.state;
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
dispatch(participantDisplayNameChanged(
|
||||
participantID, editDisplayNameValue));
|
||||
|
||||
this.setState({
|
||||
isEditing: false,
|
||||
editDisplayNameValue: ''
|
||||
});
|
||||
|
||||
this._nameInput = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal reference to the HTML element backing the React
|
||||
* {@code Component} input with id {@code editDisplayName}.
|
||||
*
|
||||
* @param {HTMLInputElement} element - The DOM/HTML element for this
|
||||
* {@code Component}'s input.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setNameInputRef(element) {
|
||||
this._nameInput = element;
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(connect()(DisplayName));
|
|
@ -0,0 +1 @@
|
|||
export { default as DisplayName } from './DisplayName';
|
|
@ -0,0 +1 @@
|
|||
export * from './components';
|
|
@ -40,6 +40,7 @@ export default class Filmstrip extends Component {
|
|||
= 'connection-indicator-container' />
|
||||
</div>
|
||||
<div className = 'videocontainer__hoverOverlay' />
|
||||
<div className = 'displayNameContainer' />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
|
|
Loading…
Reference in New Issue