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.

171 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. class AccountNote extends ImmutablePureComponent {
  37. static propTypes = {
  38. account: ImmutablePropTypes.map.isRequired,
  39. value: PropTypes.string,
  40. onSave: PropTypes.func.isRequired,
  41. intl: PropTypes.object.isRequired,
  42. };
  43. state = {
  44. value: null,
  45. saving: false,
  46. saved: false,
  47. };
  48. componentWillMount () {
  49. this._reset();
  50. }
  51. componentWillReceiveProps (nextProps) {
  52. const accountWillChange = !is(this.props.account, nextProps.account);
  53. const newState = {};
  54. if (accountWillChange && this._isDirty()) {
  55. this._save(false);
  56. }
  57. if (accountWillChange || nextProps.value === this.state.value) {
  58. newState.saving = false;
  59. }
  60. if (this.props.value !== nextProps.value) {
  61. newState.value = nextProps.value;
  62. }
  63. this.setState(newState);
  64. }
  65. componentWillUnmount () {
  66. if (this._isDirty()) {
  67. this._save(false);
  68. }
  69. }
  70. setTextareaRef = c => {
  71. this.textarea = c;
  72. };
  73. handleChange = e => {
  74. this.setState({ value: e.target.value, saving: false });
  75. };
  76. handleKeyDown = e => {
  77. if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
  78. e.preventDefault();
  79. this._save();
  80. if (this.textarea) {
  81. this.textarea.blur();
  82. }
  83. } else if (e.keyCode === 27) {
  84. e.preventDefault();
  85. this._reset(() => {
  86. if (this.textarea) {
  87. this.textarea.blur();
  88. }
  89. });
  90. }
  91. };
  92. handleBlur = () => {
  93. if (this._isDirty()) {
  94. this._save();
  95. }
  96. };
  97. _save (showMessage = true) {
  98. this.setState({ saving: true }, () => this.props.onSave(this.state.value));
  99. if (showMessage) {
  100. this.setState({ saved: true }, () => setTimeout(() => this.setState({ saved: false }), 2000));
  101. }
  102. }
  103. _reset (callback) {
  104. this.setState({ value: this.props.value }, callback);
  105. }
  106. _isDirty () {
  107. return !this.state.saving && this.props.value !== null && this.state.value !== null && this.state.value !== this.props.value;
  108. }
  109. render () {
  110. const { account, intl } = this.props;
  111. const { value, saved } = this.state;
  112. if (!account) {
  113. return null;
  114. }
  115. return (
  116. <div className='account__header__account-note'>
  117. <label htmlFor={`account-note-${account.get('id')}`}>
  118. <FormattedMessage id='account.account_note_header' defaultMessage='Note' /> <InlineAlert show={saved} />
  119. </label>
  120. <Textarea
  121. id={`account-note-${account.get('id')}`}
  122. className='account__header__account-note__content'
  123. disabled={this.props.value === null || value === null}
  124. placeholder={intl.formatMessage(messages.placeholder)}
  125. value={value || ''}
  126. onChange={this.handleChange}
  127. onKeyDown={this.handleKeyDown}
  128. onBlur={this.handleBlur}
  129. ref={this.setTextareaRef}
  130. />
  131. </div>
  132. );
  133. }
  134. }
  135. export default injectIntl(AccountNote);