ExtractedAttachments.jsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import Carousel, { Modal, ModalGateway } from 'react-images';
  4. import RefsContext from '../util/RefsContext';
  5. /**
  6. * 1. when 'fileFormat' is image, render Attachment as an image
  7. * 2. when 'fileFormat' is not image, render Attachment as an Attachment component
  8. */
  9. export default class ExtractedAttachments extends React.PureComponent {
  10. constructor(props) {
  11. super(props);
  12. this.state = {
  13. showCarousel: false,
  14. currentIndex: null,
  15. };
  16. }
  17. imageClickedHandler(index) {
  18. this.setState({
  19. showCarousel: true,
  20. currentIndex: index,
  21. });
  22. }
  23. getAttachmentsFilteredByFormat() {
  24. return this.props.attachments
  25. .filter(attachment => attachment.fileFormat.startsWith('image/'));
  26. }
  27. getClassesAndStylesForNonGrid() {
  28. const { refsContext } = this.props;
  29. const { options } = refsContext;
  30. const {
  31. width,
  32. height,
  33. 'max-width': maxWidth,
  34. 'max-height': maxHeight,
  35. display = 'block',
  36. } = options;
  37. const containerStyles = {
  38. width, height, maxWidth, maxHeight, display,
  39. };
  40. const imageClasses = [];
  41. const imageStyles = {
  42. width, height, maxWidth, maxHeight,
  43. };
  44. return {
  45. containerStyles,
  46. imageClasses,
  47. imageStyles,
  48. };
  49. }
  50. getClassesAndStylesForGrid() {
  51. const { refsContext } = this.props;
  52. const { options } = refsContext;
  53. const {
  54. 'max-width': maxWidth,
  55. 'max-height': maxHeight,
  56. } = options;
  57. const containerStyles = {
  58. width: refsContext.getOptGridWidth(),
  59. height: refsContext.getOptGridHeight(),
  60. maxWidth,
  61. maxHeight,
  62. };
  63. const imageClasses = ['w-100', 'h-100'];
  64. const imageStyles = {
  65. objectFit: 'cover',
  66. maxWidth,
  67. maxHeight,
  68. };
  69. return {
  70. containerStyles,
  71. imageClasses,
  72. imageStyles,
  73. };
  74. }
  75. /**
  76. * wrapper method for getClassesAndStylesForGrid/getClassesAndStylesForNonGrid
  77. */
  78. getClassesAndStyles() {
  79. const { refsContext } = this.props;
  80. const { options } = refsContext;
  81. return (options.grid != null)
  82. ? this.getClassesAndStylesForGrid()
  83. : this.getClassesAndStylesForNonGrid();
  84. }
  85. renderExtractedImage(attachment, index) {
  86. const { refsContext } = this.props;
  87. const { options } = refsContext;
  88. // determine alt
  89. let alt = refsContext.isSingle ? options.alt : undefined; // use only when single mode
  90. alt = alt || attachment.originalName; // use 'originalName' if options.alt is not specified
  91. // get styles
  92. const {
  93. containerStyles, imageClasses, imageStyles,
  94. } = this.getClassesAndStyles();
  95. // carousel settings
  96. let onClick;
  97. if (options['no-carousel'] == null) {
  98. // pointer cursor
  99. Object.assign(containerStyles, { cursor: 'pointer' });
  100. // set click handler
  101. onClick = () => {
  102. this.imageClickedHandler(index);
  103. };
  104. }
  105. return (
  106. <div key={attachment._id} style={containerStyles} onClick={onClick}>
  107. <img src={attachment.filePathProxied} alt={alt} className={imageClasses.join(' ')} style={imageStyles} />
  108. </div>
  109. );
  110. }
  111. renderCarousel() {
  112. const { options } = this.props.refsContext;
  113. const withCarousel = options['no-carousel'] == null;
  114. const { showCarousel, currentIndex } = this.state;
  115. const images = this.getAttachmentsFilteredByFormat()
  116. .map((attachment) => {
  117. return { src: attachment.filePathProxied };
  118. });
  119. // overwrite react-images modal styles
  120. const zIndex = 1030; // > grw-navbar
  121. const modalStyles = {
  122. blanket: (styleObj) => {
  123. return Object.assign(styleObj, { zIndex });
  124. },
  125. positioner: (styleObj) => {
  126. return Object.assign(styleObj, { zIndex });
  127. },
  128. };
  129. return (
  130. <ModalGateway>
  131. { withCarousel && showCarousel && (
  132. <Modal styles={modalStyles} onClose={() => { this.setState({ showCarousel: false }) }}>
  133. <Carousel views={images} currentIndex={currentIndex} />
  134. </Modal>
  135. ) }
  136. </ModalGateway>
  137. );
  138. }
  139. render() {
  140. const { refsContext } = this.props;
  141. const { options } = refsContext;
  142. const {
  143. grid,
  144. 'grid-gap': gridGap,
  145. } = options;
  146. const styles = {};
  147. // Grid mode
  148. if (grid != null) {
  149. const gridTemplateColumns = (refsContext.isOptGridColumnEnabled())
  150. ? `repeat(${refsContext.getOptGridColumnsNum()}, 1fr)`
  151. : `repeat(auto-fill, ${refsContext.getOptGridWidth()})`;
  152. Object.assign(styles, {
  153. display: 'grid',
  154. gridTemplateColumns,
  155. gridAutoRows: '1fr',
  156. gridGap,
  157. });
  158. }
  159. const contents = this.getAttachmentsFilteredByFormat()
  160. .map((attachment, index) => this.renderExtractedImage(attachment, index));
  161. return (
  162. <React.Fragment>
  163. <div style={styles}>
  164. {contents}
  165. </div>
  166. { this.renderCarousel() }
  167. </React.Fragment>
  168. );
  169. }
  170. }
  171. ExtractedAttachments.propTypes = {
  172. attachments: PropTypes.arrayOf(PropTypes.object).isRequired,
  173. refsContext: PropTypes.instanceOf(RefsContext).isRequired,
  174. };