1 | import React from 'react';
|
2 | import Portal from 'react-portal';
|
3 | import * as Constants from './constants';
|
4 | import brokenImage from '../assets/broken-image-placeholder.png';
|
5 | import PropTypes from 'prop-types';
|
6 |
|
7 | export class ImageCell extends React.Component {
|
8 | static propTypes = {
|
9 | cellData: PropTypes.object.isRequired,
|
10 | width: PropTypes.number.isRequired,
|
11 | mixedContentImage: PropTypes.func,
|
12 | disabled: PropTypes.bool,
|
13 | }
|
14 |
|
15 | constructor(props) {
|
16 | super(props);
|
17 |
|
18 | this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
19 | this.handleMouseLeave = this.handleMouseLeave.bind(this);
|
20 |
|
21 | this.state = {
|
22 | showPopover: false,
|
23 | imageErrored: false,
|
24 | };
|
25 | }
|
26 |
|
27 | handleMouseEnter(e) {
|
28 | const clientRect = e.target.getBoundingClientRect();
|
29 |
|
30 | this.setState({
|
31 | showPopover: true,
|
32 | popoverTop: (clientRect.top + clientRect.bottom) / 2,
|
33 | popoverLeft: clientRect.left + this.props.width - 20,
|
34 | });
|
35 | }
|
36 |
|
37 | handleMouseLeave() {
|
38 | this.setState({
|
39 | showPopover: false,
|
40 | });
|
41 | }
|
42 |
|
43 | handleImageLoaded() {
|
44 |
|
45 |
|
46 | if (this.refs.img.width * this.refs.img.height <= 25) {
|
47 | this.refs.img.src = 'about:blank';
|
48 | }
|
49 | }
|
50 |
|
51 | handleImageError() {
|
52 | this.setState({imageErrored: true});
|
53 | }
|
54 |
|
55 | render() {
|
56 | const cellData = this.props.cellData;
|
57 | let imageUrl, href, alt;
|
58 |
|
59 | if (cellData.main && typeof cellData.main === 'object') {
|
60 | imageUrl = cellData.main.src;
|
61 | href = cellData.main.href || imageUrl;
|
62 | alt = cellData.alt || cellData.main.alt;
|
63 |
|
64 | } else {
|
65 | imageUrl = cellData.main;
|
66 | href = cellData.main;
|
67 | alt = cellData.alt || cellData.main;
|
68 | }
|
69 |
|
70 | if ( ! imageUrl ) {
|
71 | return <span/>;
|
72 | }
|
73 |
|
74 | if (this.props.mixedContentImage) {
|
75 | imageUrl = this.props.mixedContentImage(imageUrl, imageUrl);
|
76 | }
|
77 | return (
|
78 | <a
|
79 | href={this.props.disabled ? 'javascript:void(0);' : href}
|
80 | title={alt}
|
81 | target="_blank"
|
82 | style={{
|
83 | display: 'inline-block',
|
84 | }}
|
85 | onMouseEnter={this.handleMouseEnter}
|
86 | onMouseLeave={this.handleMouseLeave}
|
87 | >
|
88 | <img
|
89 | ref="img"
|
90 | className="example-image"
|
91 | src={this.state.imageErrored ? brokenImage : imageUrl}
|
92 | style={{
|
93 | maxHeight: Constants.ROW_HEIGHT - 20,
|
94 | maxWidth: this.props.width - 20,
|
95 | minHeight: 5,
|
96 | minWidth: 5,
|
97 | marginTop: '-4px',
|
98 | }}
|
99 | title={alt}
|
100 | alt={alt ? alt : 'Image'}
|
101 | onLoad={this.handleImageLoaded.bind(this)}
|
102 | onError={this.handleImageError.bind(this)}
|
103 | />
|
104 | <Portal isOpened={!!(imageUrl && this.state.showPopover)}>
|
105 | <div className="popover fade right in" style={{
|
106 | display: 'block',
|
107 | position: 'absolute',
|
108 | top: this.state.popoverTop,
|
109 | left: this.state.popoverLeft,
|
110 | transform: 'translateY(-50%)',
|
111 | backgroundColor: '#fff',
|
112 | }}>
|
113 | <div className="arrow" style={{top: '50%'}}></div>
|
114 | <div className="popover-content">
|
115 | <img
|
116 | style={{width: '100%'}}
|
117 | src={this.state.imageErrored ? brokenImage : imageUrl}
|
118 | title={this.state.imageErrored ? 'Image not found' : alt}
|
119 | alt={this.state.imageErrored ? 'Image not found' : alt}
|
120 | />
|
121 | <span>{alt}</span>
|
122 | </div>
|
123 | </div>
|
124 | </Portal>
|
125 | </a>
|
126 | );
|
127 | }
|
128 | }
|