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.

170 lines
4.1 KiB

  1. import React from 'react';
  2. import ImmutablePropTypes from 'react-immutable-proptypes';
  3. import PropTypes from 'prop-types';
  4. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  5. import ImmutablePureComponent from 'react-immutable-pure-component';
  6. import Textarea from 'react-textarea-autosize';
  7. import { is } from 'immutable';
  8. const messages = defineMessages({
  9. placeholder: { id: 'account_note.placeholder', defaultMessage: 'Click to add a note' },
  10. });
  11. class InlineAlert extends React.PureComponent {
  12. static propTypes = {
  13. show: PropTypes.bool,
  14. };
  15. state = {
  16. mountMessage: false,
  17. };
  18. static TRANSITION_DELAY = 200;
  19. componentWillReceiveProps (nextProps) {
  20. if (!this.props.show && nextProps.show) {
  21. this.setState({ mountMessage: true });
  22. } else if (this.props.show && !nextProps.show) {
  23. setTimeout(() => this.setState({ mountMessage: false }), InlineAlert.TRANSITION_DELAY);
  24. }
  25. }
  26. render () {
  27. const { show } = this.props;
  28. const { mountMessage } = this.state;
  29. return (
  30. <span aria-live='polite' role='status' className='inline-alert' style={{ opacity: show ? 1 : 0 }}>
  31. {mountMessage && <FormattedMessage id='generic.saved' defaultMessage='Saved' />}
  32. </span>
  33. );
  34. }
  35. }
  36. export default @injectIntl
  37. class AccountNote extends ImmutablePureComponent {
  38. static propTypes = {
  39. account: ImmutablePropTypes.map.isRequired,
  40. value: PropTypes.string,
  41. onSave: PropTypes.func.isRequired,
  42. intl: PropTypes.object.isRequired,
  43. };
  44. state = {
  45. value: null,
  46. saving: false,
  47. saved: false,
  48. };
  49. componentWillMount () {
  50. this._reset();
  51. }
  52. componentWillReceiveProps (nextProps) {
  53. const accountWillChange = !is(this.props.account, nextProps.account);
  54. const newState = {};
  55. if (accountWillChange && this._isDirty()) {
  56. this._save(false);
  57. }
  58. if (accountWillChange || nextProps.value === this.state.value) {
  59. newState.saving = false;
  60. }
  61. if (this.props.value !== nextProps.value) {
  62. newState.value = nextProps.value;
  63. }
  64. this.setState(newState);
  65. }
  66. componentWillUnmount () {
  67. if (this._isDirty()) {
  68. this._save(false);
  69. }
  70. }
  71. setTextareaRef = c => {
  72. this.textarea = c;
  73. }
  74. handleChange = e => {
  75. this.setState({ value: e.target.value, saving: false });
  76. };
  77. handleKeyDown = e => {
  78. if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
  79. e.preventDefault();
  80. this._save();
  81. if (this.textarea) {
  82. this.textarea.blur();
  83. }
  84. } else if (e.keyCode === 27) {
  85. e.preventDefault();
  86. this._reset(() => {
  87. if (this.textarea) {
  88. this.textarea.blur();
  89. }
  90. });
  91. }
  92. }
  93. handleBlur = () => {
  94. if (this._isDirty()) {
  95. this._save();
  96. }
  97. }
  98. _save (showMessage = true) {
  99. this.setState({ saving: true }, () => this.props.onSave(this.state.value));
  100. if (showMessage) {
  101. this.setState({ saved: true }, () => setTimeout(() => this.setState({ saved: false }), 2000));
  102. }
  103. }
  104. _reset (callback) {
  105. this.setState({ value: this.props.value }, callback);
  106. }
  107. _isDirty () {
  108. return !this.state.saving && this.props.value !== null && this.state.value !== null && this.state.value !== this.props.value;
  109. }
  110. render () {
  111. const { account, intl } = this.props;
  112. const { value, saved } = this.state;
  113. if (!account) {
  114. return null;
  115. }
  116. return (
  117. <div className='account__header__account-note'>
  118. <label htmlFor={`account-note-${account.get('id')}`}>
  119. <FormattedMessage id='account.account_note_header' defaultMessage='Note' /> <InlineAlert show={saved} />
  120. </label>
  121. <Textarea
  122. id={`account-note-${account.get('id')}`}
  123. className='account__header__account-note__content'
  124. disabled={this.props.value === null || value === null}
  125. placeholder={intl.formatMessage(messages.placeholder)}
  126. value={value || ''}
  127. onChange={this.handleChange}
  128. onKeyDown={this.handleKeyDown}
  129. onBlur={this.handleBlur}
  130. ref={this.setTextareaRef}
  131. />
  132. </div>
  133. );
  134. }
  135. }