diff --git a/conference.js b/conference.js index 8ab94c3e3..8a91cee0a 100644 --- a/conference.js +++ b/conference.js @@ -2660,7 +2660,6 @@ export default { }); if (room) { - room.setDisplayName(formattedNickname); APP.UI.changeDisplayName(id, formattedNickname); } }, diff --git a/react/features/base/conference/middleware.js b/react/features/base/conference/middleware.js index 2d5c3c095..95c97bb59 100644 --- a/react/features/base/conference/middleware.js +++ b/react/features/base/conference/middleware.js @@ -15,6 +15,7 @@ import { getLocalParticipant, getParticipantById, getPinnedParticipant, + PARTICIPANT_UPDATED, PIN_PARTICIPANT } from '../participants'; import { MiddlewareRegistry, StateListenerRegistry } from '../redux'; @@ -80,6 +81,9 @@ MiddlewareRegistry.register(store => next => action => { case DATA_CHANNEL_OPENED: return _syncReceiveVideoQuality(store, next, action); + case PARTICIPANT_UPDATED: + return _updateLocalParticipantInConference(store, next, action); + case PIN_PARTICIPANT: return _pinParticipant(store, next, action); @@ -651,3 +655,27 @@ function _trackAddedOrRemoved(store, next, action) { return next(action); } + +/** + * Updates the conference object when the local participant is updated. + * + * @param {Store} store - The redux store in which the specified {@code action} + * is being dispatched. + * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the + * specified {@code action} to the specified {@code store}. + * @param {Action} action - The redux action which is being dispatched in the + * specified {@code store}. + * @private + * @returns {Object} The value returned by {@code next(action)}. + */ +function _updateLocalParticipantInConference({ getState }, next, action) { + const { conference } = getState()['features/base/conference']; + const { participant } = action; + const result = next(action); + + if (conference && participant.local) { + conference.setDisplayName(participant.name); + } + + return result; +} diff --git a/react/features/base/participants/actions.js b/react/features/base/participants/actions.js index 3d96fe983..9c11310c8 100644 --- a/react/features/base/participants/actions.js +++ b/react/features/base/participants/actions.js @@ -11,7 +11,6 @@ import { HIDDEN_PARTICIPANT_LEFT, KICK_PARTICIPANT, MUTE_REMOTE_PARTICIPANT, - PARTICIPANT_DISPLAY_NAME_CHANGED, PARTICIPANT_ID_CHANGED, PARTICIPANT_JOINED, PARTICIPANT_LEFT, @@ -208,29 +207,6 @@ export function participantConnectionStatusChanged(id, connectionStatus) { }; } -/** - * 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. * @@ -393,6 +369,10 @@ export function participantRoleChanged(id, role) { * }} */ export function participantUpdated(participant = {}) { + if (participant.name) { + participant.name = participant.name.substr(0, MAX_DISPLAY_NAME_LENGTH); + } + return { type: PARTICIPANT_UPDATED, participant diff --git a/react/features/chat/components/DisplayNameForm.web.js b/react/features/chat/components/DisplayNameForm.web.js index ad1a1d564..1e2098ad5 100644 --- a/react/features/chat/components/DisplayNameForm.web.js +++ b/react/features/chat/components/DisplayNameForm.web.js @@ -5,21 +5,13 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { translate } from '../../base/i18n'; -import { - getLocalParticipant, - participantDisplayNameChanged -} from '../../base/participants'; +import { updateSettings } from '../../base/settings'; /** * The type of the React {@code Component} props of {@DisplayNameForm}. */ type Props = { - /** - * The ID of the local participant. - */ - _localParticipantId: string, - /** * Invoked to set the local participant display name. */ @@ -117,26 +109,11 @@ class DisplayNameForm extends Component { _onSubmit(event: Object) { event.preventDefault(); - this.props.dispatch(participantDisplayNameChanged( - this.props._localParticipantId, - this.state.displayName)); + // Store display name in settings + this.props.dispatch(updateSettings({ + displayName: this.state.displayName + })); } } -/** - * Maps (parts of) the Redux state to the associated props for the - * {@code DisplayNameForm} component. - * - * @param {Object} state - The Redux state. - * @private - * @returns {{ - * _localParticipantId: string - * }} - */ -function _mapStateToProps(state) { - return { - _localParticipantId: getLocalParticipant(state).id - }; -} - -export default translate(connect(_mapStateToProps)(DisplayNameForm)); +export default translate(connect()(DisplayNameForm)); diff --git a/react/features/display-name/actions.js b/react/features/display-name/actions.js index 39dbc4c3b..536dd007e 100644 --- a/react/features/display-name/actions.js +++ b/react/features/display-name/actions.js @@ -1,3 +1,5 @@ +// @flow + import { openDialog } from '../../features/base/dialog'; import { DisplayNamePrompt } from './components'; @@ -5,8 +7,12 @@ import { DisplayNamePrompt } from './components'; /** * Signals to open a dialog with the {@code DisplayNamePrompt} component. * + * @param {?Function} onPostSubmit - The function to invoke after a successful + * submit of the dialog. * @returns {Object} */ -export function openDisplayNamePrompt() { - return openDialog(DisplayNamePrompt); +export function openDisplayNamePrompt(onPostSubmit: ?Function = undefined) { + return openDialog(DisplayNamePrompt, { + onPostSubmit + }); } diff --git a/react/features/display-name/components/AbstractDisplayNamePrompt.js b/react/features/display-name/components/AbstractDisplayNamePrompt.js new file mode 100644 index 000000000..49f68a4d0 --- /dev/null +++ b/react/features/display-name/components/AbstractDisplayNamePrompt.js @@ -0,0 +1,74 @@ +// @flow + +import { Component } from 'react'; + +import { updateSettings } from '../../base/settings'; + +/** + * The type of the React {@code Component} props of + * {@link AbstractDisplayNamePrompt}. + */ +export type Props = { + + /** + * Invoked to update the local participant's display name. + */ + dispatch: Dispatch<*>, + + /** + * Function to be invoked after a successful display name change. + */ + onPostSubmit: ?Function, + + /** + * Invoked to obtain translated strings. + */ + t: Function +}; + +/** + * Implements an abstract class for {@code DisplayNamePrompt}. + */ +export default class AbstractDisplayNamePrompt + extends Component { + /** + * Instantiates a new component. + * + * @inheritdoc + */ + constructor(props: Props) { + super(props); + + this._onSetDisplayName = this._onSetDisplayName.bind(this); + } + + _onSetDisplayName: string => boolean; + + /** + * Dispatches an action to update the local participant's display name. A + * name must be entered for the action to dispatch. + * + * It returns a boolean to comply the Dialog behaviour: + * {@code true} - the dialog should be closed. + * {@code false} - the dialog should be left open. + * + * @param {string} displayName - The display name to save. + * @returns {boolean} + */ + _onSetDisplayName(displayName) { + if (!displayName || !displayName.trim()) { + return false; + } + + const { dispatch, onPostSubmit } = this.props; + + // Store display name in settings + dispatch(updateSettings({ + displayName + })); + + onPostSubmit && onPostSubmit(); + + return true; + } +} diff --git a/react/features/display-name/components/DisplayName.web.js b/react/features/display-name/components/DisplayName.web.js index 1afb2bfd0..6ad6189a0 100644 --- a/react/features/display-name/components/DisplayName.web.js +++ b/react/features/display-name/components/DisplayName.web.js @@ -6,13 +6,21 @@ import { connect } from 'react-redux'; import { appendSuffix } from '../functions'; import { translate } from '../../base/i18n'; -import { participantDisplayNameChanged } from '../../base/participants'; +import { + getParticipantDisplayName +} from '../../base/participants'; +import { updateSettings } from '../../base/settings'; /** * The type of the React {@code Component} props of {@link DisplayName}. */ type Props = { + /** + * The participant's current display name. + */ + _displayName: string, + /** * Whether or not the display name should be editable on click. */ @@ -23,11 +31,6 @@ type Props = { */ dispatch: Dispatch<*>, - /** - * The participant's current display name. - */ - displayName: string, - /** * A string to append to the displayName, if provided. */ @@ -130,8 +133,8 @@ class DisplayName extends Component { */ render() { const { + _displayName, allowEditing, - displayName, displayNameSuffix, elementID, t @@ -159,7 +162,7 @@ class DisplayName extends Component { className = 'displayname' id = { elementID } onClick = { this._onStartEditing }> - { appendSuffix(displayName, displayNameSuffix) } + { appendSuffix(_displayName, displayNameSuffix) } ); } @@ -208,7 +211,7 @@ class DisplayName extends Component { if (this.props.allowEditing) { this.setState({ isEditing: true, - editDisplayNameValue: this.props.displayName || '' + editDisplayNameValue: this.props._displayName || '' }); } } @@ -226,10 +229,12 @@ class DisplayName extends Component { */ _onSubmit() { const { editDisplayNameValue } = this.state; - const { dispatch, participantID } = this.props; + const { dispatch } = this.props; - dispatch(participantDisplayNameChanged( - participantID, editDisplayNameValue)); + // Store display name in settings + dispatch(updateSettings({ + displayName: editDisplayNameValue + })); this.setState({ isEditing: false, @@ -255,4 +260,23 @@ class DisplayName extends Component { } } -export default translate(connect()(DisplayName)); +/** + * Maps (parts of) the redux state to the props of this component. + * + * @param {Object} state - The redux store/state. + * @param {Props} ownProps - The own props of the component. + * @private + * @returns {{ + * _displayName: string + * }} + */ +function _mapStateToProps(state, ownProps) { + const { participantID } = ownProps; + + return { + _displayName: getParticipantDisplayName( + state, participantID) + }; +} + +export default translate(connect(_mapStateToProps)(DisplayName)); diff --git a/react/features/display-name/components/DisplayNamePrompt.web.js b/react/features/display-name/components/DisplayNamePrompt.web.js index b0b11cfa8..f10f8d458 100644 --- a/react/features/display-name/components/DisplayNamePrompt.web.js +++ b/react/features/display-name/components/DisplayNamePrompt.web.js @@ -1,37 +1,15 @@ /* @flow */ -import React, { Component } from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { FieldTextStateless as TextField } from '@atlaskit/field-text'; import { Dialog } from '../../base/dialog'; import { translate } from '../../base/i18n'; -import { - getLocalParticipant, - participantDisplayNameChanged -} from '../../base/participants'; -/** - * The type of the React {@code Component} props of {@link DisplayNamePrompt}. - */ -type Props = { - - /** - * The current ID for the local participant. Used for setting the display - * name on the associated participant. - */ - _localParticipantID: string, - - /** - * Invoked to update the local participant's display name. - */ - dispatch: Dispatch<*>, - - /** - * Invoked to obtain translated strings. - */ - t: Function -}; +import AbstractDisplayNamePrompt, { + type Props +} from './AbstractDisplayNamePrompt'; /** * The type of the React {@code Component} props of {@link DisplayNamePrompt}. @@ -50,7 +28,7 @@ type State = { * * @extends Component */ -class DisplayNamePrompt extends Component { +class DisplayNamePrompt extends AbstractDisplayNamePrompt { /** * Initializes a new {@code DisplayNamePrompt} instance. * @@ -110,6 +88,8 @@ class DisplayNamePrompt extends Component { }); } + _onSetDisplayName: string => boolean; + _onSubmit: () => boolean; /** @@ -120,42 +100,8 @@ class DisplayNamePrompt extends Component { * @returns {boolean} */ _onSubmit() { - const { displayName } = this.state; - - if (!displayName.trim()) { - return false; - } - - const { dispatch, _localParticipantID } = this.props; - - dispatch( - participantDisplayNameChanged(_localParticipantID, displayName)); - - return true; + return this._onSetDisplayName(this.state.displayName); } } -/** - * Maps (parts of) the Redux state to the associated {@code DisplayNamePrompt}'s - * props. - * - * @param {Object} state - The Redux state. - * @private - * @returns {{ - * _localParticipantID: string - * }} - */ -function _mapStateToProps(state) { - const { id } = getLocalParticipant(state); - - return { - /** - * The current ID for the local participant. - * - * @type {string} - */ - _localParticipantID: id - }; -} - -export default translate(connect(_mapStateToProps)(DisplayNamePrompt)); +export default translate(connect()(DisplayNamePrompt)); diff --git a/react/features/display-name/functions.js b/react/features/display-name/functions.js index 9e167b0b1..d3b661da2 100644 --- a/react/features/display-name/functions.js +++ b/react/features/display-name/functions.js @@ -7,5 +7,5 @@ */ export function appendSuffix(displayName, suffix) { return `${displayName || suffix || ''}${ - displayName && suffix ? ` (${suffix})` : ''}`; + displayName && suffix && displayName !== suffix ? ` (${suffix})` : ''}`; }