Skip to content

Commit f2abe60

Browse files
authored
Merge pull request #56 from Wikia/advanced-image-view
Advanced Image View
2 parents edf536a + ef29d5a commit f2abe60

File tree

19 files changed

+920
-348
lines changed

19 files changed

+920
-348
lines changed

config/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
'theme/index.js',
1818
'theme/typography.js',
1919
'utils/eventLogger.js',
20+
'utils/vignette.js',
2021
],
2122
externalDependencies: [
2223
'date-fns',

config/styleguide.config.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
"HideComponent",
6161
"Icon",
6262
"IconSprite",
63+
"Image",
64+
"ImagePreloader",
6365
"Input",
6466
"List",
6567
"Select",
@@ -69,8 +71,7 @@
6971
"Switch.Item",
7072
"Switch.Wrapper",
7173
"Timeago",
72-
"VideoPlayIcon",
73-
"Vignette"
74+
"VideoPlayIcon"
7475
]
7576
},
7677
{
@@ -90,6 +91,15 @@
9091
"useLazyLoad"
9192
]
9293
},
94+
{
95+
"name": "Utils",
96+
"sections": [
97+
{
98+
"name": "Vignette",
99+
"content": "../source/utils/VIGNETTE.md"
100+
}
101+
]
102+
},
93103
{
94104
"name": "Models",
95105
"description": "Wrappers for immutable.js Records.",

source/components/Image/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Default:
2+
3+
```js
4+
<Image
5+
alt="test"
6+
src="https://static.wikia.nocookie.net/2a6c845c-c6e9-472e-abed-24863817b795"
7+
width="160"
8+
height="100"
9+
/>
10+
```
11+
12+
Disable lazy loading
13+
14+
```js
15+
<Image
16+
alt="test"
17+
src="https://static.wikia.nocookie.net/2a6c845c-c6e9-472e-abed-24863817b795"
18+
disableLazy
19+
/>
20+
```
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Disabled lazyLoading 1`] = `
4+
<Image
5+
alt="test"
6+
disableLazy={true}
7+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
8+
>
9+
<img
10+
alt="test"
11+
className=""
12+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
13+
/>
14+
<noscript>
15+
<img
16+
alt="test"
17+
className=""
18+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
19+
/>
20+
</noscript>
21+
</Image>
22+
`;
23+
24+
exports[`Image test 1`] = `
25+
<Image
26+
alt="test"
27+
disableLazy={false}
28+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
29+
>
30+
<ImagePreloader
31+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
32+
srcSet={null}
33+
>
34+
<img
35+
alt="test"
36+
className=""
37+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205/smart/width/5/height/5"
38+
/>
39+
</ImagePreloader>
40+
<noscript>
41+
<img
42+
alt="test"
43+
className=""
44+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
45+
/>
46+
</noscript>
47+
</Image>
48+
`;
49+
50+
exports[`Image test for non-vignette 1`] = `
51+
<Image
52+
alt="test"
53+
disableLazy={false}
54+
src="https://foo.jpg"
55+
>
56+
<img
57+
alt="test"
58+
className=""
59+
src="https://foo.jpg"
60+
/>
61+
</Image>
62+
`;
63+
64+
exports[`Updating an image src 1`] = `
65+
<Image
66+
alt="test"
67+
disableLazy={false}
68+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
69+
>
70+
<ImagePreloader
71+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
72+
srcSet={null}
73+
>
74+
<img
75+
alt="test"
76+
className=""
77+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205/smart/width/5/height/5"
78+
/>
79+
</ImagePreloader>
80+
<noscript>
81+
<img
82+
alt="test"
83+
className=""
84+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
85+
/>
86+
</noscript>
87+
</Image>
88+
`;

source/components/Image/index.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import classNames from 'classnames';
2+
import PropTypes from 'prop-types';
3+
import React from 'react';
4+
5+
import ImagePreloader from '../ImagePreloader';
6+
import { isVignetteUrl, vignette } from '../../utils/vignette';
7+
8+
const LAZY_IMAGE_SIZE = 5;
9+
10+
class Image extends React.Component {
11+
static propTypes = {
12+
alt: PropTypes.string.isRequired,
13+
className: PropTypes.string,
14+
disableLazy: PropTypes.bool,
15+
src: PropTypes.string.isRequired,
16+
srcSet: PropTypes.string,
17+
};
18+
19+
static defaultProps = {
20+
className: undefined,
21+
disableLazy: false,
22+
srcSet: undefined,
23+
};
24+
25+
state = {
26+
src: this.props.src,
27+
isLimbo: false,
28+
};
29+
30+
// When the src changes first replace the src with a temp image so it doesn't stall displaying
31+
// the old image
32+
// https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html#when-to-use-derived-state
33+
static getDerivedStateFromProps(props, state) {
34+
// when only the src changes we are in "limbo" mode
35+
return {
36+
isLimbo: props.src !== state.src,
37+
};
38+
}
39+
40+
// after the component updates once we want to
41+
componentDidUpdate() {
42+
if (this.props.src !== this.state.src) {
43+
// this is one of the rare cases to conditionally call setState after a component update
44+
// this allows the images to be removed from the DOM properly
45+
// eslint-disable-next-line react/no-did-update-set-state
46+
this.setState(() => ({ src: this.props.src }));
47+
}
48+
}
49+
50+
/**
51+
* Create a super low resolution image that will automatically be blurred in most browsers
52+
*/
53+
getLowResSrcFromVignette() {
54+
return vignette(this.props.src).withSmart(LAZY_IMAGE_SIZE, LAZY_IMAGE_SIZE).get();
55+
}
56+
57+
renderPlainImage() {
58+
const { src, alt, className, disableLazy, ...rest } = this.props;
59+
60+
return <img className={classNames(className)} src={src} alt={alt} {...rest} />;
61+
}
62+
63+
renderVignetteImage() {
64+
const { src: _skip1, srcSet: _skip2, alt, className, disableLazy, ...rest } = this.props;
65+
66+
if (disableLazy) {
67+
return this.renderPlainImage();
68+
}
69+
70+
return (
71+
<ImagePreloader src={this.state.src} srcSet={this.props.srcSet}>
72+
{({ src, srcSet, state }) => {
73+
// we will not test the functionality of ImagePreloader here
74+
/* istanbul ignore next */
75+
if (state !== ImagePreloader.STATE.PENDING) {
76+
return <img className={classNames(className)} src={src} srcSet={srcSet} alt={alt} {...rest} />;
77+
}
78+
79+
// if the image is loading, render low quality image
80+
return <img className={classNames(className)} src={this.getLowResSrcFromVignette()} alt={alt} {...rest} />;
81+
}}
82+
</ImagePreloader>
83+
);
84+
}
85+
86+
render() {
87+
const { src, isLimbo } = this.state;
88+
89+
if (isVignetteUrl(src)) {
90+
// Limbo state happens when only the src and/or srcset is changed
91+
// there is no standard on how to handle the state of the image when the src is changed across browsers
92+
// lets just remove the entire node from html when in limbo
93+
return (
94+
<React.Fragment>
95+
{!isLimbo && this.renderVignetteImage()}
96+
{/* // support no-js and SSR */}
97+
<noscript>
98+
{this.renderPlainImage()}
99+
</noscript>
100+
</React.Fragment>
101+
);
102+
}
103+
104+
// if the image is not a Vignette one, just render it and don't care
105+
return this.renderPlainImage();
106+
}
107+
}
108+
109+
export default Image;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { mount } from 'enzyme';
2+
import React from 'react';
3+
4+
import Image from './index';
5+
6+
7+
// jest.mock('../ImagePreloader', () => mockComponent('ImagePreloader'));
8+
9+
function mountImage(additionalProps = {}) {
10+
const wrapper = mount(
11+
<Image
12+
alt="test"
13+
src="http://vignette.wikia-dev.us/22b12324-ab36-4266-87c9-452776157205"
14+
{...additionalProps}
15+
/>,
16+
);
17+
return wrapper;
18+
}
19+
20+
test('Image test', () => {
21+
const component = mountImage();
22+
expect(component).toMatchSnapshot();
23+
});
24+
25+
test('Image test for non-vignette', () => {
26+
const component = mountImage({ src: 'https://foo.jpg' });
27+
expect(component).toMatchSnapshot();
28+
});
29+
30+
test('Updating an image src', () => {
31+
const component = mountImage();
32+
33+
component.setState({ src: 'test' });
34+
component.update();
35+
36+
expect(component).toMatchSnapshot();
37+
});
38+
39+
test('Disabled lazyLoading', () => {
40+
const component = mountImage({ disableLazy: true });
41+
42+
expect(component).toMatchSnapshot();
43+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
`ImagePreloader` component can be used to load (or preload) any image. Both `onChange` and children are functions that are called with `ImagePreloader`'s state with the following:
2+
3+
* `state` - the current state of the preloading - can be either `pending`, `success` or `error`
4+
All the states are xported via `ImagePreloader.STATE` const.
5+
* `error` - a JavaScript `Error` object (if the `state` is `error`) or null
6+
* `src` - taken from `ImagePreloader`'s props
7+
* `srcSet` - taken from `ImagePreloader`'s props
8+
9+
### Usage:
10+
11+
```js static
12+
import ImagePreloader from '@wikia/react-common/components/ImagePreloader';
13+
14+
export default ImageWithLoadingMessage = () => (
15+
<ImagePreloader src="http://path.to.image.jpg">
16+
{({ state, src }) => {
17+
if (state === ImagePreloader.STATE.PENDING) {
18+
return <span>Loading image...</span>;
19+
}
20+
21+
if (state === ImagePreloader.STATE.ERROR) {
22+
return <span>Error loading image</span>;
23+
}
24+
25+
return <img src={src} />;
26+
}}
27+
</ImagePreloader>
28+
);
29+
30+
```

0 commit comments

Comments
 (0)