From 6d7d98c149e33e58e941f968697ca44d1e4c1b3a Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Tue, 8 Mar 2016 18:03:45 -0800 Subject: [PATCH] [add] support directly requiring image assets Thanks to IjzerenHein . See #84 --- docs/guides/react-native.md | 19 +++++++++++ src/components/Image/__tests__/index-test.js | 36 +++++++++++++++++--- src/components/Image/index.js | 28 +++++++++------ src/components/Image/resolveAssetSource.js | 5 +++ 4 files changed, 72 insertions(+), 16 deletions(-) create mode 100644 src/components/Image/resolveAssetSource.js diff --git a/docs/guides/react-native.md b/docs/guides/react-native.md index cb0e24da..9b926405 100644 --- a/docs/guides/react-native.md +++ b/docs/guides/react-native.md @@ -16,6 +16,25 @@ module.exports = { } ``` +## Image assets + +In order to require image assets (e.g. `require('assets/myimage.png')`), add +the `url-loader` to the webpack config: + +```js +// webpack.config.js + +module.exports = { + // ... + module: { + loaders: { + test: /\.(gif|jpe?g|png|svg)$/, + loader: 'url-loader', + query: { name: '[name].[hash:16].[ext]' } + } + } +``` + ## Web-specific code Minor platform differences can use the `Platform` module. diff --git a/src/components/Image/__tests__/index-test.js b/src/components/Image/__tests__/index-test.js index 996f4f4d..c4831a0f 100644 --- a/src/components/Image/__tests__/index-test.js +++ b/src/components/Image/__tests__/index-test.js @@ -36,6 +36,14 @@ suite('components/Image', () => { assert(backgroundImage.indexOf(defaultSource.uri) > -1) }) + test('prop "defaultSource" with string value"', () => { + // emulate require-ed asset + const defaultSource = 'https://google.com/favicon.ico' + const dom = utils.renderToDOM() + const backgroundImage = dom.style.backgroundImage + assert(backgroundImage.indexOf(defaultSource) > -1) + }) + test('prop "onError"', function (done) { this.timeout(5000) utils.render( { test('prop "onLoad"', function (done) { this.timeout(5000) - utils.render() + utils.render() function onLoad(e) { assert.equal(e.nativeEvent.type, 'load') done() @@ -91,7 +96,28 @@ suite('components/Image', () => { }) }) - test('prop "source"') + test('prop "source"', function (done) { + this.timeout(5000) + const source = { uri: 'https://google.com/favicon.ico' } + utils.render() + function onLoad(e) { + const src = e.nativeEvent.target.src + assert.equal(src, source.uri) + done() + } + }) + + test('prop "source" with string value', function (done) { + this.timeout(5000) + // emulate require-ed asset + const source = 'https://google.com/favicon.ico' + utils.render() + function onLoad(e) { + const src = e.nativeEvent.target.src + assert.equal(src, source) + done() + } + }) suite('prop "style"', () => { test('converts "resizeMode" property', () => { diff --git a/src/components/Image/index.js b/src/components/Image/index.js index e1cbfcd3..89e17522 100644 --- a/src/components/Image/index.js +++ b/src/components/Image/index.js @@ -1,5 +1,6 @@ /* global window */ import { NativeMethodsDecorator } from '../../modules/NativeMethodsMixin' +import resolveAssetSource from './resolveAssetSource' import CoreComponent from '../CoreComponent' import ImageResizeMode from './ImageResizeMode' import ImageStylePropTypes from './ImageStylePropTypes' @@ -14,27 +15,32 @@ const STATUS_LOADING = 'LOADING' const STATUS_PENDING = 'PENDING' const STATUS_IDLE = 'IDLE' +const ImageSourcePropType = PropTypes.oneOfType([ + PropTypes.shape({ + uri: PropTypes.string.isRequired + }), + PropTypes.string +]) + @NativeMethodsDecorator export default class Image extends Component { static propTypes = { accessibilityLabel: CoreComponent.propTypes.accessibilityLabel, accessible: CoreComponent.propTypes.accessible, children: PropTypes.any, - defaultSource: PropTypes.object, + defaultSource: ImageSourcePropType, onError: PropTypes.func, onLoad: PropTypes.func, onLoadEnd: PropTypes.func, onLoadStart: PropTypes.func, resizeMode: PropTypes.oneOf(['contain', 'cover', 'none', 'stretch']), - source: PropTypes.object, + source: ImageSourcePropType, style: StyleSheetPropType(ImageStylePropTypes), testID: CoreComponent.propTypes.testID }; static defaultProps = { accessible: true, - defaultSource: {}, - source: {}, style: {} }; @@ -42,7 +48,7 @@ export default class Image extends Component { constructor(props, context) { super(props, context) - const { uri } = props.source + const uri = resolveAssetSource(props.source) // state this.state = { status: uri ? STATUS_PENDING : STATUS_IDLE } // autobinding @@ -51,13 +57,13 @@ export default class Image extends Component { } _createImageLoader() { - const { source } = this.props + const uri = resolveAssetSource(this.props.source) this._destroyImageLoader() this.image = new window.Image() this.image.onerror = this._onError this.image.onload = this._onLoad - this.image.src = source.uri + this.image.src = uri this._onLoadStart() } @@ -113,9 +119,10 @@ export default class Image extends Component { } componentWillReceiveProps(nextProps) { - if (this.props.source.uri !== nextProps.source.uri) { + const nextUri = resolveAssetSource(nextProps.source) + if (resolveAssetSource(this.props.source) !== nextUri) { this.setState({ - status: nextProps.source.uri ? STATUS_PENDING : STATUS_IDLE + status: nextUri ? STATUS_PENDING : STATUS_IDLE }) } } @@ -135,8 +142,7 @@ export default class Image extends Component { } = this.props const isLoaded = this.state.status === STATUS_LOADED - const defaultImage = defaultSource.uri || null - const displayImage = !isLoaded ? defaultImage : source.uri + const displayImage = resolveAssetSource(!isLoaded ? defaultSource : source) const backgroundImage = displayImage ? `url("${displayImage}")` : null const style = StyleSheet.flatten(this.props.style) diff --git a/src/components/Image/resolveAssetSource.js b/src/components/Image/resolveAssetSource.js new file mode 100644 index 00000000..f0ad2dc5 --- /dev/null +++ b/src/components/Image/resolveAssetSource.js @@ -0,0 +1,5 @@ +function resolveAssetSource(source) { + return ((typeof source === 'object') ? source.uri : source) || null +} + +export default resolveAssetSource