ProfileImageSettings.jsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { withTranslation } from 'react-i18next';
  4. import md5 from 'md5';
  5. import { toastSuccess, toastError } from '../../util/apiNotification';
  6. import { createSubscribedElement } from '../UnstatedUtils';
  7. import AppContainer from '../../services/AppContainer';
  8. import PersonalContainer from '../../services/PersonalContainer';
  9. import ImageCropModal from './ImageCropModal';
  10. class ProfileImageSettings extends React.Component {
  11. constructor(appContainer) {
  12. super();
  13. this.state = {
  14. show: false,
  15. src: null,
  16. };
  17. this.imageRef = null;
  18. this.onSelectFile = this.onSelectFile.bind(this);
  19. this.hideModal = this.hideModal.bind(this);
  20. this.cancelModal = this.cancelModal.bind(this);
  21. this.onCropCompleted = this.onCropCompleted.bind(this);
  22. this.onClickSubmit = this.onClickSubmit.bind(this);
  23. }
  24. async onClickSubmit() {
  25. const { t, personalContainer } = this.props;
  26. try {
  27. await personalContainer.updateProfileImage();
  28. toastSuccess(t('toaster.update_successed', { target: t('Set Profile Image') }));
  29. }
  30. catch (err) {
  31. toastError(err);
  32. }
  33. }
  34. generateGravatarSrc() {
  35. const email = this.props.personalContainer.state.email || '';
  36. const hash = md5(email.trim().toLowerCase());
  37. return `https://gravatar.com/avatar/${hash}`;
  38. }
  39. onSelectFile(e) {
  40. if (e.target.files && e.target.files.length > 0) {
  41. const reader = new FileReader();
  42. reader.addEventListener('load', () => this.setState({ src: reader.result }));
  43. reader.readAsDataURL(e.target.files[0]);
  44. this.setState({ show: true });
  45. }
  46. }
  47. async onCropCompleted(croppedImageUrl) {
  48. const { t, personalContainer } = this.props;
  49. personalContainer.setState({ croppedImageUrl });
  50. try {
  51. await personalContainer.uploadAttachment(croppedImageUrl);
  52. toastSuccess(t('toaster.update_successed', { target: t('Upload Image') }));
  53. }
  54. catch (err) {
  55. toastError(err);
  56. }
  57. this.hideModal();
  58. }
  59. showModal() {
  60. this.setState({ show: true });
  61. }
  62. hideModal() {
  63. this.setState({ show: false });
  64. }
  65. cancelModal() {
  66. this.hideModal();
  67. }
  68. render() {
  69. const { t, personalContainer } = this.props;
  70. const { uploadedPictureSrc, isGravatarEnabled } = personalContainer.state;
  71. return (
  72. <React.Fragment>
  73. <div className="row">
  74. <div className="col-md-2 col-sm-offset-1 col-sm-4">
  75. <h4>
  76. <div className="radio radio-primary">
  77. <input
  78. type="radio"
  79. id="radioGravatar"
  80. form="formImageType"
  81. name="imagetypeForm[isGravatarEnabled]"
  82. checked={isGravatarEnabled}
  83. onChange={() => { personalContainer.changeIsGravatarEnabled(true) }}
  84. />
  85. <label htmlFor="radioGravatar">
  86. <img src="https://gravatar.com/avatar/00000000000000000000000000000000?s=24" /> Gravatar
  87. </label>
  88. <a href="https://gravatar.com/">
  89. <small><i className="icon-arrow-right-circle" aria-hidden="true"></i></small>
  90. </a>
  91. </div>
  92. </h4>
  93. <img src={this.generateGravatarSrc()} width="64" />
  94. </div>
  95. <div className="col-md-4 col-sm-7">
  96. <h4>
  97. <div className="radio radio-primary">
  98. <input
  99. type="radio"
  100. id="radioUploadPicture"
  101. form="formImageType"
  102. name="imagetypeForm[isGravatarEnabled]"
  103. checked={!isGravatarEnabled}
  104. onChange={() => { personalContainer.changeIsGravatarEnabled(false) }}
  105. />
  106. <label htmlFor="radioUploadPicture">
  107. { t('Upload Image') }
  108. </label>
  109. </div>
  110. </h4>
  111. <div className="row mb-3">
  112. <label className="col-sm-4 control-label">
  113. { t('Current Image') }
  114. </label>
  115. <div className="col-sm-8">
  116. {uploadedPictureSrc && (<p><img src={uploadedPictureSrc} className="picture picture-lg img-circle" id="settingUserPicture" /></p>)}
  117. {/* TODO GW-1218 create apiV3 for delete image */}
  118. <button type="button" className="btn btn-danger">{ t('Delete Image') }</button>
  119. </div>
  120. </div>
  121. <div className="row">
  122. <label className="col-sm-4 control-label">
  123. {t('Upload new image')}
  124. </label>
  125. <div className="col-sm-8">
  126. <input type="file" onChange={this.onSelectFile} name="profileImage" accept="image/*" />
  127. </div>
  128. </div>
  129. </div>
  130. </div>
  131. <ImageCropModal
  132. show={this.state.show}
  133. src={this.state.src}
  134. onModalClose={this.cancelModal}
  135. onCropCompleted={this.onCropCompleted}
  136. />
  137. <div className="row my-3">
  138. <div className="col-xs-offset-4 col-xs-5">
  139. <button type="button" className="btn btn-primary" onClick={this.onClickSubmit} disabled={personalContainer.state.retrieveError != null}>
  140. {t('Update')}
  141. </button>
  142. </div>
  143. </div>
  144. </React.Fragment>
  145. );
  146. }
  147. }
  148. const ProfileImageSettingsWrapper = (props) => {
  149. return createSubscribedElement(ProfileImageSettings, props, [AppContainer, PersonalContainer]);
  150. };
  151. ProfileImageSettings.propTypes = {
  152. t: PropTypes.func.isRequired, // i18next
  153. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  154. personalContainer: PropTypes.instanceOf(PersonalContainer).isRequired,
  155. };
  156. export default withTranslation()(ProfileImageSettingsWrapper);