crop.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import React from "react"
  2. import Avatar from "misago/components/avatar"
  3. import Button from "misago/components/button"
  4. import ajax from "misago/services/ajax"
  5. import snackbar from "misago/services/snackbar"
  6. export default class extends React.Component {
  7. constructor(props) {
  8. super(props)
  9. this.state = {
  10. isLoading: false,
  11. deviceRatio: 1,
  12. }
  13. }
  14. getAvatarSize() {
  15. if (this.props.upload) {
  16. return this.props.options.crop_tmp.size
  17. } else {
  18. return this.props.options.crop_src.size
  19. }
  20. }
  21. getImagePath() {
  22. if (this.props.upload) {
  23. return this.props.dataUrl
  24. } else {
  25. return this.props.options.crop_src.url
  26. }
  27. }
  28. componentDidMount() {
  29. let cropit = $(".crop-form")
  30. let cropperWidth = this.getAvatarSize()
  31. const initialWidth = cropit.width()
  32. while (initialWidth < cropperWidth) {
  33. cropperWidth = cropperWidth / 2
  34. }
  35. const deviceRatio = this.getAvatarSize() / cropperWidth
  36. cropit.width(cropperWidth)
  37. cropit.cropit({
  38. width: cropperWidth,
  39. height: cropperWidth,
  40. exportZoom: deviceRatio,
  41. imageState: {
  42. src: this.getImagePath(),
  43. },
  44. onImageLoaded: () => {
  45. if (this.props.upload) {
  46. // center uploaded image
  47. let zoomLevel = cropit.cropit("zoom")
  48. let imageSize = cropit.cropit("imageSize")
  49. // is it wider than taller?
  50. if (imageSize.width > imageSize.height) {
  51. let displayedWidth = imageSize.width * zoomLevel
  52. let offsetX = (displayedWidth - this.getAvatarSize()) / -2
  53. cropit.cropit("offset", {
  54. x: offsetX,
  55. y: 0,
  56. })
  57. } else if (imageSize.width < imageSize.height) {
  58. let displayedHeight = imageSize.height * zoomLevel
  59. let offsetY = (displayedHeight - this.getAvatarSize()) / -2
  60. cropit.cropit("offset", {
  61. x: 0,
  62. y: offsetY,
  63. })
  64. } else {
  65. cropit.cropit("offset", {
  66. x: 0,
  67. y: 0,
  68. })
  69. }
  70. } else {
  71. // use preserved crop
  72. let crop = this.props.options.crop_src.crop
  73. if (crop) {
  74. cropit.cropit("zoom", crop.zoom)
  75. cropit.cropit("offset", {
  76. x: crop.x,
  77. y: crop.y,
  78. })
  79. }
  80. }
  81. },
  82. })
  83. }
  84. componentWillUnmount() {
  85. $(".crop-form").cropit("disable")
  86. }
  87. cropAvatar = () => {
  88. if (this.state.isLoading) {
  89. return false
  90. }
  91. this.setState({
  92. isLoading: true,
  93. })
  94. let avatarType = this.props.upload ? "crop_tmp" : "crop_src"
  95. let cropit = $(".crop-form")
  96. const deviceRatio = cropit.cropit("exportZoom")
  97. const cropitOffset = cropit.cropit("offset")
  98. ajax
  99. .post(this.props.user.api.avatar, {
  100. avatar: avatarType,
  101. crop: {
  102. offset: {
  103. x: cropitOffset.x * deviceRatio,
  104. y: cropitOffset.y * deviceRatio,
  105. },
  106. zoom: cropit.cropit("zoom") * deviceRatio,
  107. },
  108. })
  109. .then(
  110. (data) => {
  111. this.props.onComplete(data)
  112. snackbar.success(data.detail)
  113. },
  114. (rejection) => {
  115. if (rejection.status === 400) {
  116. snackbar.error(rejection.detail)
  117. this.setState({
  118. isLoading: false,
  119. })
  120. } else {
  121. this.props.showError(rejection)
  122. }
  123. }
  124. )
  125. }
  126. render() {
  127. return (
  128. <div>
  129. <div className="modal-body modal-avatar-crop">
  130. <div className="crop-form">
  131. <div className="cropit-preview" />
  132. <input type="range" className="cropit-image-zoom-input" />
  133. </div>
  134. </div>
  135. <div className="modal-footer">
  136. <div className="col-md-6 col-md-offset-3">
  137. <Button
  138. onClick={this.cropAvatar}
  139. loading={this.state.isLoading}
  140. className="btn-primary btn-block"
  141. >
  142. {this.props.upload
  143. ? gettext("Set avatar")
  144. : gettext("Crop image")}
  145. </Button>
  146. <Button
  147. onClick={this.props.showIndex}
  148. disabled={this.state.isLoading}
  149. className="btn-default btn-block"
  150. >
  151. {gettext("Cancel")}
  152. </Button>
  153. </div>
  154. </div>
  155. </div>
  156. )
  157. }
  158. }