import React from 'react'; import Style from '@localTypes/style'; import { LayoutableProps, ClickableProps } from '../types'; import View from './view'; import { prefetch, getSize } from '../modules/image-loader-module'; import { Device } from '../native'; import { colorParse, colorArrayParse } from '../color'; import { warn } from '../utils'; interface Size { width: number; height: number; } interface ImageSource { uri: string; } interface ImageProps extends LayoutableProps, ClickableProps { /** * Single image source */ src?: string; /** * Image source object */ source?: ImageSource | ImageSource[]; srcs?: string[]; sources?: ImageSource[]; /** * Image placeholder when image is loading. * Support base64 image only. */ defaultSource?: string; /** * Fill color to the image */ tintColor?: number | string; tintColors?: number[] | string[]; /** * Image style when `Image` have other children. */ imageStyle?: Style; /** * Image ref when `Image` have other children. */ imageRef?: React.ReactNode; /** * Image resize mode, as same as containMode */ resizeMode?: 'cover' | 'contain' | 'stretch' | 'repeat' | 'center'; /** * When the image is resized, the corners of the size specified by capInsets * will stay a fixed size, but the center content and borders of the image will be stretched. * This is useful for creating resizable rounded buttons, shadows, and other resizable assets. */ capInsets?: { top: number; right: number; bottom: number; left: number; }; style: Style; /** * Invoked on `Image` is loaded. */ onLoad?(): void; /** * Invoke on `Image` is end of loading. */ onLoadEnd?(): void; /** * Invoke on `Image` is start to loading. */ onLoadStart?(): void; /** * Invoke on loading of `Image` get error. * * @param {Object} evt - Loading error data. * @param {string} evt.nativeEvent.error - Loading error message. */ onError?(evt: { nativeEvent: { error: string }}): void; /** * Invoke on Image is loading. * * @param {Object} evt - Image loading progress data. * @param {number} evt.nativeEvent.loaded - The image is loaded. * @param {number} evt.nativeEvent.total - The loadded progress. */ onProgress?(evt: { nativeEvent: { loaded: number; total: number }}): void; } function handleImgUrl(url: string): string { if (url && !/^(http|https):\/\//.test(url) && url.indexOf('assets') > -1) { if (process.env.NODE_ENV === 'dev') { const addStr1 = 'http://'; // do not change this, otherwise js-min went wrong return `${addStr1}127.0.0.1:${process.env.PORT}/${url}`; } const addStr2 = 'hpfile://'; // do not change this, otherwise js-min went wrong return `${addStr2}./${url}`; } return url; } /** * A React component for displaying different types of images, including network images, * static resources, temporary local images, and images from local disk, such as the camera roll. * @noInheritDoc */ class Image extends React.Component { static get resizeMode() { return { contain: 'contain', cover: 'cover', stretch: 'stretch', center: 'center', repeat: 'repeat', // iOS Only }; } static getSize( url: string, success: (width: number, height: number) => void, failure: (err: typeof Error) => void, ) { if (typeof url !== 'string') { throw new TypeError('Image.getSize first argument must be a string url'); } const size = getSize(url); if (typeof success === 'function') { size.then((result: Size) => success(result.width, result.height)); } if (typeof failure === 'function') { size.catch(failure); } else { size.catch((err: Error) => warn(`Failed to get size for image: ${url}`, err)); } return size; } static prefetch = prefetch; /** * @ignore */ public render() { const { children, style, imageStyle, imageRef, source, sources, src, srcs, tintColor, tintColors, ...nativeProps } = this.props; /** * Image source prop */ // Define the image source url array. let imageUrls: string[] = []; if (typeof src === 'string') { imageUrls.push(src); } if (Array.isArray(srcs)) { imageUrls = [...imageUrls, ...srcs]; } if (source) { if (typeof source === 'string') { imageUrls.push(source); } else if (typeof source === 'object' && source !== null) { const { uri } = source as ImageSource; if (uri) { imageUrls.push(uri); } } } if (sources) { if (Array.isArray(sources)) { sources.forEach((imageSrc) => { if (typeof imageSrc === 'string') { imageUrls.push(imageSrc); } else if (typeof imageSrc === 'object' && imageSrc !== null && imageSrc.uri) { imageUrls.push(imageSrc.uri); } }); } } if (imageUrls.length) { imageUrls = imageUrls.map(url => handleImgUrl(url)); } // Set sources props by platform specification if (Device.platform.OS === 'ios') { if (imageUrls.length) { (nativeProps as ImageProps).source = imageUrls.map(uri => ({ uri })); } } else if (Device.platform.OS === 'android') { if (imageUrls.length === 1) { [(nativeProps as ImageProps).src] = imageUrls; } else if (imageUrls.length > 1) { (nativeProps as ImageProps).srcs = imageUrls; } } /** * defaultSource prop */ if (typeof nativeProps.defaultSource === 'string') { if (nativeProps.defaultSource.indexOf('data:image/') !== 0) { warn('[Image] defaultSource prop must be a local base64 image'); } nativeProps.defaultSource = handleImgUrl(nativeProps.defaultSource); } /** * tintColor(s) */ const nativeStyle = { ...style }; if (tintColor) { nativeStyle.tintColor = colorParse(tintColor); } if (Array.isArray(tintColors)) { nativeStyle.tintColors = colorArrayParse(tintColors); } (nativeProps as ImageProps).style = nativeStyle; if (children) { return ( {children} ); } return ( ); } } export default Image;