You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DisplayName.js 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /* @flow */
  2. import { withStyles } from '@material-ui/styles';
  3. import React, { Component } from 'react';
  4. import type { Dispatch } from 'redux';
  5. import { translate } from '../../../base/i18n';
  6. import {
  7. getParticipantDisplayName,
  8. getParticipantById
  9. } from '../../../base/participants';
  10. import { connect } from '../../../base/redux';
  11. import { updateSettings } from '../../../base/settings';
  12. import { Tooltip } from '../../../base/tooltip';
  13. import { getIndicatorsTooltipPosition } from '../../../filmstrip/functions.web';
  14. import { appendSuffix } from '../../functions';
  15. /**
  16. * The type of the React {@code Component} props of {@link DisplayName}.
  17. */
  18. type Props = {
  19. /**
  20. * The participant's current display name which should be shown when in
  21. * edit mode. Can be different from what is shown when not editing.
  22. */
  23. _configuredDisplayName: string,
  24. /**
  25. * The participant's current display name which should be shown.
  26. */
  27. _nameToDisplay: string,
  28. /**
  29. * Whether or not the display name should be editable on click.
  30. */
  31. allowEditing: boolean,
  32. /**
  33. * Invoked to update the participant's display name.
  34. */
  35. dispatch: Dispatch<any>,
  36. /**
  37. * A string to append to the displayName, if provided.
  38. */
  39. displayNameSuffix: string,
  40. /**
  41. * An object containing the CSS classes.
  42. */
  43. classes: Object,
  44. /**
  45. * The ID attribute to add to the component. Useful for global querying for
  46. * the component by legacy components and torture tests.
  47. */
  48. elementID: string,
  49. /**
  50. * The ID of the participant whose name is being displayed.
  51. */
  52. participantID: string,
  53. /**
  54. * Invoked to obtain translated strings.
  55. */
  56. t: Function,
  57. /**
  58. * The type of thumbnail.
  59. */
  60. thumbnailType: string
  61. };
  62. /**
  63. * The type of the React {@code Component} state of {@link DisplayName}.
  64. */
  65. type State = {
  66. /**
  67. * The current value of the display name in the edit field.
  68. */
  69. editDisplayNameValue: string,
  70. /**
  71. * Whether or not the component should be displaying an editable input.
  72. */
  73. isEditing: boolean
  74. };
  75. const styles = theme => {
  76. return {
  77. displayName: {
  78. ...theme.typography.labelBold,
  79. lineHeight: `${theme.typography.labelBold.lineHeight}px`,
  80. color: theme.palette.text01,
  81. overflow: 'hidden',
  82. textOverflow: 'ellipsis',
  83. whiteSpace: 'nowrap'
  84. },
  85. editDisplayName: {
  86. outline: 'none',
  87. border: 'none',
  88. background: 'none',
  89. boxShadow: 'none',
  90. padding: 0,
  91. ...theme.typography.labelBold,
  92. lineHeight: `${theme.typography.labelBold.lineHeight}px`,
  93. color: theme.palette.text01
  94. }
  95. };
  96. };
  97. /**
  98. * React {@code Component} for displaying and editing a participant's name.
  99. *
  100. * @augments Component
  101. */
  102. class DisplayName extends Component<Props, State> {
  103. _nameInput: ?HTMLInputElement;
  104. static defaultProps = {
  105. _configuredDisplayName: ''
  106. };
  107. /**
  108. * Initializes a new {@code DisplayName} instance.
  109. *
  110. * @param {Object} props - The read-only properties with which the new
  111. * instance is to be initialized.
  112. */
  113. constructor(props: Props) {
  114. super(props);
  115. this.state = {
  116. editDisplayNameValue: '',
  117. isEditing: false
  118. };
  119. /**
  120. * The internal reference to the HTML element backing the React
  121. * {@code Component} input with id {@code editDisplayName}. It is
  122. * necessary for automatically selecting the display name input field
  123. * when starting to edit the display name.
  124. *
  125. * @private
  126. * @type {HTMLInputElement}
  127. */
  128. this._nameInput = null;
  129. // Bind event handlers so they are only bound once for every instance.
  130. this._onChange = this._onChange.bind(this);
  131. this._onKeyDown = this._onKeyDown.bind(this);
  132. this._onStartEditing = this._onStartEditing.bind(this);
  133. this._onSubmit = this._onSubmit.bind(this);
  134. this._setNameInputRef = this._setNameInputRef.bind(this);
  135. }
  136. /**
  137. * Automatically selects the input field's value after starting to edit the
  138. * display name.
  139. *
  140. * @inheritdoc
  141. * @returns {void}
  142. */
  143. componentDidUpdate(previousProps, previousState) {
  144. if (!previousState.isEditing
  145. && this.state.isEditing
  146. && this._nameInput) {
  147. this._nameInput.select();
  148. }
  149. }
  150. /**
  151. * Implements React's {@link Component#render()}.
  152. *
  153. * @inheritdoc
  154. * @returns {ReactElement}
  155. */
  156. render() {
  157. const {
  158. _nameToDisplay,
  159. allowEditing,
  160. displayNameSuffix,
  161. classes,
  162. elementID,
  163. t,
  164. thumbnailType
  165. } = this.props;
  166. if (allowEditing && this.state.isEditing) {
  167. return (
  168. <input
  169. autoFocus = { true }
  170. className = { classes.editDisplayName }
  171. id = 'editDisplayName'
  172. onBlur = { this._onSubmit }
  173. onChange = { this._onChange }
  174. onClick = { this._onClick }
  175. onKeyDown = { this._onKeyDown }
  176. placeholder = { t('defaultNickname') }
  177. ref = { this._setNameInputRef }
  178. spellCheck = { 'false' }
  179. type = 'text'
  180. value = { this.state.editDisplayNameValue } />
  181. );
  182. }
  183. return (
  184. <Tooltip
  185. content = { appendSuffix(_nameToDisplay, displayNameSuffix) }
  186. position = { getIndicatorsTooltipPosition(thumbnailType) }>
  187. <span
  188. className = { `displayname ${classes.displayName}` }
  189. id = { elementID }
  190. onClick = { this._onStartEditing }>
  191. { appendSuffix(_nameToDisplay, displayNameSuffix) }
  192. </span>
  193. </Tooltip>
  194. );
  195. }
  196. /**
  197. * Stop click event propagation.
  198. *
  199. * @param {MouseEvent} e - The click event.
  200. * @private
  201. * @returns {void}
  202. */
  203. _onClick(e) {
  204. e.stopPropagation();
  205. }
  206. _onChange: () => void;
  207. /**
  208. * Updates the internal state of the display name entered into the edit
  209. * field.
  210. *
  211. * @param {Object} event - DOM Event for value change.
  212. * @private
  213. * @returns {void}
  214. */
  215. _onChange(event) {
  216. this.setState({
  217. editDisplayNameValue: event.target.value
  218. });
  219. }
  220. _onKeyDown: () => void;
  221. /**
  222. * Submits the edited display name update if the enter key is pressed.
  223. *
  224. * @param {Event} event - Key down event object.
  225. * @private
  226. * @returns {void}
  227. */
  228. _onKeyDown(event) {
  229. if (event.key === 'Enter') {
  230. this._onSubmit();
  231. }
  232. }
  233. _onStartEditing: () => void;
  234. /**
  235. * Updates the component to display an editable input field and sets the
  236. * initial value to the current display name.
  237. *
  238. * @param {MouseEvent} e - The click event.
  239. * @private
  240. * @returns {void}
  241. */
  242. _onStartEditing(e) {
  243. if (this.props.allowEditing) {
  244. e.stopPropagation();
  245. this.setState({
  246. isEditing: true,
  247. editDisplayNameValue: this.props._configuredDisplayName
  248. });
  249. }
  250. }
  251. _onSubmit: () => void;
  252. /**
  253. * Dispatches an action to update the display name if any change has
  254. * occurred after editing. Clears any temporary state used to keep track
  255. * of pending display name changes and exits editing mode.
  256. *
  257. * @param {Event} event - Key down event object.
  258. * @private
  259. * @returns {void}
  260. */
  261. _onSubmit() {
  262. const { editDisplayNameValue } = this.state;
  263. const { dispatch } = this.props;
  264. // Store display name in settings
  265. dispatch(updateSettings({
  266. displayName: editDisplayNameValue
  267. }));
  268. this.setState({
  269. isEditing: false,
  270. editDisplayNameValue: ''
  271. });
  272. this._nameInput = null;
  273. }
  274. _setNameInputRef: (HTMLInputElement | null) => void;
  275. /**
  276. * Sets the internal reference to the HTML element backing the React
  277. * {@code Component} input with id {@code editDisplayName}.
  278. *
  279. * @param {HTMLInputElement} element - The DOM/HTML element for this
  280. * {@code Component}'s input.
  281. * @private
  282. * @returns {void}
  283. */
  284. _setNameInputRef(element) {
  285. this._nameInput = element;
  286. }
  287. }
  288. /**
  289. * Maps (parts of) the redux state to the props of this component.
  290. *
  291. * @param {Object} state - The redux store/state.
  292. * @param {Props} ownProps - The own props of the component.
  293. * @private
  294. * @returns {{
  295. * _configuredDisplayName: string,
  296. * _nameToDisplay: string
  297. * }}
  298. */
  299. function _mapStateToProps(state, ownProps) {
  300. const { participantID } = ownProps;
  301. const participant = getParticipantById(state, participantID);
  302. return {
  303. _configuredDisplayName: participant && participant.name,
  304. _nameToDisplay: getParticipantDisplayName(state, participantID)
  305. };
  306. }
  307. export default translate(connect(_mapStateToProps)(withStyles(styles)(DisplayName)));