diff --git a/packages/react-native-web/src/exports/Image/ImageUriCache.js b/packages/react-native-web/src/exports/Image/ImageUriCache.js
index 32cb1be50..0cb8335fe 100644
--- a/packages/react-native-web/src/exports/Image/ImageUriCache.js
+++ b/packages/react-native-web/src/exports/Image/ImageUriCache.js
@@ -7,36 +7,59 @@
* @flow
*/
-const dataUriPattern = /^data:/;
+import ImageLoader from '../../modules/ImageLoader';
+
+type ImageSource =
+ | string
+ | number
+ | {
+ method: ?string,
+ uri: ?string,
+ headers: ?Object,
+ body: ?string
+ };
export default class ImageUriCache {
static _maximumEntries: number = 256;
static _entries = {};
- static has(uri: string) {
+ static createCacheId(source: ImageSource) {
+ return JSON.stringify(ImageLoader.resolveSource(source));
+ }
+
+ static has(source: ImageSource) {
+ const entries = ImageUriCache._entries;
+ const cacheId = ImageUriCache.createCacheId(source);
+ return Boolean(entries[cacheId]);
+ }
+
+ static get(source: ImageSource) {
const entries = ImageUriCache._entries;
- const isDataUri = dataUriPattern.test(uri);
- return isDataUri || Boolean(entries[uri]);
+ const cacheId = ImageUriCache.createCacheId(source);
+ return entries[cacheId];
}
- static add(uri: string) {
+ static add(source: ImageSource, displayImageUri: ?string) {
const entries = ImageUriCache._entries;
const lastUsedTimestamp = Date.now();
- if (entries[uri]) {
- entries[uri].lastUsedTimestamp = lastUsedTimestamp;
- entries[uri].refCount += 1;
+ const cacheId = ImageUriCache.createCacheId(source);
+ if (entries[cacheId]) {
+ entries[cacheId].lastUsedTimestamp = lastUsedTimestamp;
+ entries[cacheId].refCount += 1;
} else {
- entries[uri] = {
+ entries[cacheId] = {
lastUsedTimestamp,
- refCount: 1
+ refCount: 1,
+ displayImageUri: displayImageUri || ImageLoader.resolveSource(source).uri
};
}
}
- static remove(uri: string) {
+ static remove(source: ImageSource) {
const entries = ImageUriCache._entries;
- if (entries[uri]) {
- entries[uri].refCount -= 1;
+ const cacheId = ImageUriCache.createCacheId(source);
+ if (entries[cacheId]) {
+ entries[cacheId].refCount -= 1;
}
// Free up entries when the cache is "full"
ImageUriCache._cleanUpIfNeeded();
@@ -44,20 +67,20 @@ export default class ImageUriCache {
static _cleanUpIfNeeded() {
const entries = ImageUriCache._entries;
- const imageUris = Object.keys(entries);
+ const cacheIds = Object.keys(entries);
- if (imageUris.length + 1 > ImageUriCache._maximumEntries) {
+ if (cacheIds.length + 1 > ImageUriCache._maximumEntries) {
let leastRecentlyUsedKey;
let leastRecentlyUsedEntry;
- imageUris.forEach(uri => {
- const entry = entries[uri];
+ cacheIds.forEach(cacheId => {
+ const entry = entries[cacheId];
if (
(!leastRecentlyUsedEntry ||
entry.lastUsedTimestamp < leastRecentlyUsedEntry.lastUsedTimestamp) &&
entry.refCount === 0
) {
- leastRecentlyUsedKey = uri;
+ leastRecentlyUsedKey = cacheId;
leastRecentlyUsedEntry = entry;
}
});
diff --git a/packages/react-native-web/src/exports/Image/__tests__/index-test.js b/packages/react-native-web/src/exports/Image/__tests__/index-test.js
index b9dfa9ae6..560537773 100644
--- a/packages/react-native-web/src/exports/Image/__tests__/index-test.js
+++ b/packages/react-native-web/src/exports/Image/__tests__/index-test.js
@@ -8,7 +8,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import StyleSheet from '../../StyleSheet';
-const originalImage = window.Image;
+const OriginalImage = window.Image;
const findImageSurfaceStyle = wrapper => StyleSheet.flatten(wrapper.childAt(0).prop('style'));
@@ -19,7 +19,7 @@ describe('components/Image', () => {
});
afterEach(() => {
- window.Image = originalImage;
+ window.Image = OriginalImage;
});
test('prop "accessibilityLabel"', () => {
@@ -95,11 +95,12 @@ describe('components/Image', () => {
describe('prop "onLoad"', () => {
test('is called after image is loaded from network', () => {
jest.useFakeTimers();
+ const uri = 'https://test.com/img.jpg';
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
- onLoad();
+ onLoad(uri);
});
const onLoadStub = jest.fn();
- shallow();
+ shallow();
jest.runOnlyPendingTimers();
expect(ImageLoader.load).toBeCalled();
expect(onLoadStub).toBeCalled();
@@ -107,11 +108,11 @@ describe('components/Image', () => {
test('is called after image is loaded from cache', () => {
jest.useFakeTimers();
+ const uri = 'https://test.com/img.jpg';
ImageLoader.load = jest.fn().mockImplementation((_, onLoad, onError) => {
- onLoad();
+ onLoad(uri);
});
const onLoadStub = jest.fn();
- const uri = 'https://test.com/img.jpg';
ImageUriCache.add(uri);
shallow();
jest.runOnlyPendingTimers();
@@ -164,7 +165,7 @@ describe('components/Image', () => {
test('is set immediately if the image was preloaded', () => {
const uri = 'https://yahoo.com/favicon.ico';
ImageLoader.load = jest.fn().mockImplementationOnce((_, onLoad, onError) => {
- onLoad();
+ onLoad(uri);
});
return Image.prefetch(uri).then(() => {
const source = { uri };
@@ -222,7 +223,7 @@ describe('components/Image', () => {
});
const component = shallow();
expect(component.find('img').prop('src')).toBe(defaultUri);
- loadCallback();
+ loadCallback(uri);
expect(component.find('img').prop('src')).toBe(uri);
});
});
diff --git a/packages/react-native-web/src/exports/Image/index.js b/packages/react-native-web/src/exports/Image/index.js
index 2262a68e1..6e612fbe5 100644
--- a/packages/react-native-web/src/exports/Image/index.js
+++ b/packages/react-native-web/src/exports/Image/index.js
@@ -11,7 +11,6 @@
import applyNativeMethods from '../../modules/applyNativeMethods';
import createElement from '../createElement';
import css from '../StyleSheet/css';
-import { getAssetByID } from '../../modules/AssetRegistry';
import resolveShadowValue from '../StyleSheet/resolveShadowValue';
import ImageLoader from '../../modules/ImageLoader';
import ImageResizeMode from './ImageResizeMode';
@@ -33,46 +32,15 @@ const STATUS_LOADING = 'LOADING';
const STATUS_PENDING = 'PENDING';
const STATUS_IDLE = 'IDLE';
-const getImageState = (uri, shouldDisplaySource) => {
- return shouldDisplaySource ? STATUS_LOADED : uri ? STATUS_PENDING : STATUS_IDLE;
+const getImageState = (source, shouldDisplaySource) => {
+ return shouldDisplaySource ? STATUS_LOADED : source ? STATUS_PENDING : STATUS_IDLE;
};
const resolveAssetDimensions = source => {
- if (typeof source === 'number') {
- const { height, width } = getAssetByID(source);
- return { height, width };
- } else if (typeof source === 'object') {
- const { height, width } = source;
- return { height, width };
- }
-};
-
-const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
-const resolveAssetUri = source => {
- let uri = '';
- if (typeof source === 'number') {
- // get the URI from the packager
- const asset = getAssetByID(source);
- const scale = asset.scales[0];
- const scaleSuffix = scale !== 1 ? `@${scale}x` : '';
- uri = asset ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` : '';
- } else if (typeof source === 'string') {
- uri = source;
- } else if (source && typeof source.uri === 'string') {
- uri = source.uri;
- }
-
- if (uri) {
- const match = uri.match(svgDataUriPattern);
- // inline SVG markup may contain characters (e.g., #, ") that need to be escaped
- if (match) {
- const [, prefix, svg] = match;
- const encodedSvg = encodeURIComponent(svg);
- return `${prefix}${encodedSvg}`;
- }
- }
-
- return uri;
+ return {
+ height: source.height,
+ width: source.width
+ };
};
let filterId = 0;
@@ -91,7 +59,8 @@ const createTintColorSVG = (tintColor, id) =>
type State = {
layout: Object,
- shouldDisplaySource: boolean
+ shouldDisplaySource: boolean,
+ displayImageUri: string
};
class Image extends Component<*, State> {
@@ -130,10 +99,10 @@ class Image extends Component<*, State> {
}
static prefetch(uri) {
- return ImageLoader.prefetch(uri).then(() => {
+ return ImageLoader.prefetch(uri).then(displayImageUri => {
// Add the uri to the cache so it can be immediately displayed when used
// but also immediately remove it to correctly reflect that it has no active references
- ImageUriCache.add(uri);
+ ImageUriCache.add(uri, displayImageUri);
ImageUriCache.remove(uri);
});
}
@@ -157,10 +126,18 @@ class Image extends Component<*, State> {
constructor(props, context) {
super(props, context);
// If an image has been loaded before, render it immediately
- const uri = resolveAssetUri(props.source);
- const shouldDisplaySource = ImageUriCache.has(uri);
- this.state = { layout: {}, shouldDisplaySource };
- this._imageState = getImageState(uri, shouldDisplaySource);
+ const resolvedSource = ImageLoader.resolveSource(props.source);
+ const resolvedDefaultSource = ImageLoader.resolveSource(props.defaultSource);
+ const cachedSource = ImageUriCache.get(props.source);
+ const shouldDisplaySource = !!cachedSource;
+ this.state = {
+ layout: {},
+ shouldDisplaySource,
+ displayImageUri: shouldDisplaySource
+ ? cachedSource.displayImageUri
+ : resolvedDefaultSource.uri || resolvedSource.uri
+ };
+ this._imageState = getImageState(props.source, shouldDisplaySource);
this._filterId = filterId;
filterId++;
}
@@ -170,20 +147,21 @@ class Image extends Component<*, State> {
if (this._imageState === STATUS_PENDING) {
this._createImageLoader();
} else if (this._imageState === STATUS_LOADED) {
- this._onLoad({ target: this._imageRef });
+ this._onLoad(this.state.displayImageUri, { target: this._imageRef });
}
}
componentDidUpdate(prevProps) {
- const prevUri = resolveAssetUri(prevProps.source);
- const uri = resolveAssetUri(this.props.source);
- const hasDefaultSource = this.props.defaultSource != null;
- if (prevUri !== uri) {
- ImageUriCache.remove(prevUri);
- const isPreviouslyLoaded = ImageUriCache.has(uri);
- isPreviouslyLoaded && ImageUriCache.add(uri);
- this._updateImageState(getImageState(uri, isPreviouslyLoaded), hasDefaultSource);
- } else if (hasDefaultSource && prevProps.defaultSource !== this.props.defaultSource) {
+ const { defaultSource, source } = this.props;
+ const prevCacheId = ImageUriCache.createCacheId(prevProps.source);
+ const cacheId = ImageUriCache.createCacheId(source);
+ const hasDefaultSource = defaultSource != null;
+ if (prevCacheId !== cacheId) {
+ ImageUriCache.remove(prevProps.source);
+ const shouldDisplaySource = ImageUriCache.has(source);
+ shouldDisplaySource && ImageUriCache.add(source);
+ this._updateImageState(getImageState(source, shouldDisplaySource), hasDefaultSource);
+ } else if (hasDefaultSource && prevProps.defaultSource !== defaultSource) {
this._updateImageState(this._imageState, hasDefaultSource);
}
if (this._imageState === STATUS_PENDING) {
@@ -192,14 +170,13 @@ class Image extends Component<*, State> {
}
componentWillUnmount() {
- const uri = resolveAssetUri(this.props.source);
- ImageUriCache.remove(uri);
+ ImageUriCache.remove(this.props.source);
this._destroyImageLoader();
this._isMounted = false;
}
render() {
- const { shouldDisplaySource } = this.state;
+ const { displayImageUri, shouldDisplaySource } = this.state;
const {
accessibilityLabel,
accessible,
@@ -233,8 +210,7 @@ class Image extends Component<*, State> {
}
}
- const selectedSource = shouldDisplaySource ? source : defaultSource;
- const displayImageUri = resolveAssetUri(selectedSource);
+ const selectedSource = ImageLoader.resolveSource(shouldDisplaySource ? source : defaultSource);
const imageSizeStyle = resolveAssetDimensions(selectedSource);
const backgroundImage = displayImageUri ? `url("${displayImageUri}")` : null;
const flatStyle = { ...StyleSheet.flatten(this.props.style) };
@@ -312,8 +288,12 @@ class Image extends Component<*, State> {
_createImageLoader() {
const { source } = this.props;
this._destroyImageLoader();
- const uri = resolveAssetUri(source);
- this._imageRequestId = ImageLoader.load(uri, this._onLoad, this._onError);
+ this._imageRequestId = ImageLoader.load(
+ ImageLoader.resolveSource(source),
+ this._onLoad,
+ this._onError,
+ this._onProgress
+ );
this._onLoadStart();
}
@@ -350,23 +330,33 @@ class Image extends Component<*, State> {
}
};
+ _onProgress = event => {
+ const { onProgress } = this.props;
+ if (onProgress) {
+ onProgress({
+ nativeEvent: event
+ });
+ }
+ };
+
_onError = () => {
const { onError, source } = this.props;
this._updateImageState(STATUS_ERRORED);
if (onError) {
onError({
nativeEvent: {
- error: `Failed to load resource ${resolveAssetUri(source)} (404)`
+ error: `Failed to load resource ${ImageLoader.resolveSource(source).uri} (404)`
}
});
}
this._onLoadEnd();
};
- _onLoad = e => {
+ _onLoad = (displayImageUri, e) => {
const { onLoad, source } = this.props;
const event = { nativeEvent: e };
- ImageUriCache.add(resolveAssetUri(source));
+
+ ImageUriCache.add(source, displayImageUri);
this._updateImageState(STATUS_LOADED);
if (onLoad) {
onLoad(event);
@@ -394,14 +384,29 @@ class Image extends Component<*, State> {
};
_updateImageState(status: ?string, hasDefaultSource: ?boolean = false) {
+ const { source, defaultSource } = this.props;
+ const resolvedSource = ImageLoader.resolveSource(defaultSource);
+ const resolvedDefaultSource = ImageLoader.resolveSource(source);
this._imageState = status;
const shouldDisplaySource =
this._imageState === STATUS_LOADED ||
(this._imageState === STATUS_LOADING && !hasDefaultSource);
+ const { displayImageUri } = ImageUriCache.has(source)
+ ? ImageUriCache.get(source)
+ : {
+ displayImageUri: resolvedSource.uri || resolvedDefaultSource.uri
+ };
+
// only triggers a re-render when the image is loading and has no default image (to support PJPEG), loaded, or failed
- if (shouldDisplaySource !== this.state.shouldDisplaySource) {
+ if (
+ shouldDisplaySource !== this.state.shouldDisplaySource ||
+ displayImageUri !== this.state.displayImageUri
+ ) {
if (this._isMounted) {
- this.setState(() => ({ shouldDisplaySource }));
+ this.setState(() => ({
+ shouldDisplaySource,
+ displayImageUri
+ }));
}
}
}
diff --git a/packages/react-native-web/src/modules/ImageLoader/index.js b/packages/react-native-web/src/modules/ImageLoader/index.js
index 72a77662b..df78ce533 100644
--- a/packages/react-native-web/src/modules/ImageLoader/index.js
+++ b/packages/react-native-web/src/modules/ImageLoader/index.js
@@ -7,8 +7,12 @@
* @noflow
*/
+import { getAssetByID } from '../AssetRegistry';
+
let id = 0;
const requests = {};
+const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/;
+const dataUriPattern = /^data:/;
const ImageLoader = {
abort(requestId: number) {
@@ -21,7 +25,7 @@ const ImageLoader = {
getSize(uri, success, failure) {
let complete = false;
const interval = setInterval(callback, 16);
- const requestId = ImageLoader.load(uri, callback, errorCallback);
+ const requestId = ImageLoader.load({ uri }, callback, errorCallback);
function callback() {
const image = requests[`${requestId}`];
@@ -46,13 +50,17 @@ const ImageLoader = {
clearInterval(interval);
}
},
- load(uri, onLoad, onError): number {
+ load(source, onLoad, onError, onProgress): number {
+ const { uri, method, headers, body } = { uri: '', method: 'GET', headers: {}, ...source };
id += 1;
+
+ // Create image
const image = new window.Image();
image.onerror = onError;
image.onload = e => {
// avoid blocking the main thread
- const onDecode = () => onLoad(e);
+ const onDecode = () => onLoad(image.src, e);
+
if (typeof image.decode === 'function') {
// Safari currently throws exceptions when decoding svgs.
// We want to catch that error and allow the load handler
@@ -62,14 +70,97 @@ const ImageLoader = {
setTimeout(onDecode, 0);
}
};
- image.src = uri;
requests[`${id}`] = image;
+
+ // If the important source properties are empty, return the image directly
+ if (!source || !uri) {
+ return id;
+ }
+
+ // If the image is a dataUri, display it directly via image
+ const isDataUri = dataUriPattern.test(uri);
+ if (isDataUri) {
+ image.src = uri;
+ return id;
+ }
+
+ // If the image can be retrieved via GET, we can fallback to image loading method
+ if (method === 'GET') {
+ image.src = uri;
+ return id;
+ }
+
+ // Load image via XHR
+ const request = new window.XMLHttpRequest();
+ request.open(method, uri);
+ request.responseType = 'blob';
+ request.withCredentials = false;
+ request.onerror = () => {
+ // Fall back to image (e.g. for CORS issues)
+ image.src = uri;
+ };
+
+ // Add request headers
+ for (const [name, value] of Object.entries(headers)) {
+ request.setRequestHeader(name, value);
+ }
+
+ // When the request finished loading, pass it on to the image
+ request.onload = () => {
+ image.src = window.URL.createObjectURL(request.response);
+ };
+
+ // Track progress
+ request.onprogress = onProgress;
+
+ // Send the request
+ request.send(body);
+
return id;
},
prefetch(uri): Promise {
return new Promise((resolve, reject) => {
- ImageLoader.load(uri, resolve, reject);
+ ImageLoader.load({ uri }, resolve, reject);
});
+ },
+ resolveSource(source) {
+ let resolvedSource = {
+ method: 'GET',
+ uri: '',
+ headers: {},
+ width: undefined,
+ height: undefined
+ };
+ if (typeof source === 'number') {
+ // get the URI from the packager
+ const asset = getAssetByID(source);
+ const scale = asset.scales[0];
+ const scaleSuffix = scale !== 1 ? `@${scale}x` : '';
+ resolvedSource.uri = asset
+ ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}`
+ : '';
+ resolvedSource.width = asset.width;
+ resolvedSource.height = asset.height;
+ } else if (typeof source === 'string') {
+ resolvedSource.uri = source;
+ } else if (typeof source === 'object') {
+ resolvedSource = {
+ ...resolvedSource,
+ ...source
+ };
+ }
+
+ if (resolvedSource.uri) {
+ const match = resolvedSource.uri.match(svgDataUriPattern);
+ // inline SVG markup may contain characters (e.g., #, ") that need to be escaped
+ if (match) {
+ const [, prefix, svg] = match;
+ const encodedSvg = encodeURIComponent(svg);
+ resolvedSource.uri = `${prefix}${encodedSvg}`;
+ }
+ }
+
+ return resolvedSource;
}
};
diff --git a/packages/website/storybook/1-components/Image/ImageScreen.js b/packages/website/storybook/1-components/Image/ImageScreen.js
index f72a55dc3..f87bbf0c3 100644
--- a/packages/website/storybook/1-components/Image/ImageScreen.js
+++ b/packages/website/storybook/1-components/Image/ImageScreen.js
@@ -11,6 +11,7 @@ import PropOnError from './examples/PropOnError';
import PropOnLoad from './examples/PropOnLoad';
import PropOnLoadEnd from './examples/PropOnLoadEnd';
import PropOnLoadStart from './examples/PropOnLoadStart';
+import PropOnProgress from './examples/PropOnProgress';
import PropResizeMode from './examples/PropResizeMode';
import PropSource from './examples/PropSource';
import StaticGetSizeExample from './examples/StaticGetSize';
@@ -105,6 +106,19 @@ const ImageScreen = () => (
}}
/>
+
+ Invoked on download progress with {'{nativeEvent: {loaded, total}}'}
.
+
+ }
+ example={{
+ render: () =>
+ }}
+ />
+
- {this.state.message && {this.state.message}}
+ {this.state.messages.map((message, index) => {
+ return (
+
+ {message}
+
+ );
+ })}
);
}
_handleError = e => {
- const nextState = { loading: false };
- if (this.props.logMethod === 'onError') {
- nextState.message = `✘ onError ${JSON.stringify(e.nativeEvent)}`;
- }
- this.setState(() => nextState);
+ this.setState(state => {
+ const messages = [...state.messages];
+ if (this.props.logMethod === 'onError') {
+ messages.push(`✘ onError ${JSON.stringify(e.nativeEvent)}`);
+ }
+
+ return {
+ loading: false,
+ messages
+ };
+ });
};
_handleLoad = () => {
- const nextState = { loading: false };
- if (this.props.logMethod === 'onLoad') {
- nextState.message = '✔ onLoad';
- }
- this.setState(() => nextState);
+ this.setState(state => {
+ const messages = [...state.messages];
+ if (this.props.logMethod === 'onLoad') {
+ messages.push('✔ onLoad');
+ }
+
+ return {
+ loading: false,
+ messages
+ };
+ });
};
_handleLoadEnd = () => {
- const nextState = { loading: false };
- if (this.props.logMethod === 'onLoadEnd') {
- nextState.message = '✔ onLoadEnd';
- }
- this.setState(() => nextState);
+ this.setState(state => {
+ const messages = [...state.messages];
+ if (this.props.logMethod === 'onLoadEnd') {
+ messages.push('✔ onLoadEnd');
+ }
+
+ return {
+ loading: false,
+ messages
+ };
+ });
};
_handleLoadStart = () => {
- const nextState = { loading: true };
- if (this.props.logMethod === 'onLoadStart') {
- nextState.message = '✔ onLoadStart';
- }
- this.setState(() => nextState);
+ this.setState(state => {
+ const messages = [...state.messages];
+ if (this.props.logMethod === 'onLoadStart') {
+ messages.push('✔ onLoadStart');
+ }
+
+ return {
+ loading: false,
+ messages
+ };
+ });
+ };
+
+ _handleProgress = e => {
+ this.setState(state => {
+ const messages = [...state.messages];
+ if (this.props.logMethod === 'onProgress') {
+ const { loaded, total } = e.nativeEvent;
+ messages.push(
+ `✔ onProgress ${JSON.stringify({
+ loaded,
+ total
+ })}`
+ );
+ }
+
+ return {
+ messages
+ };
+ });
};
}
diff --git a/packages/website/storybook/1-components/Image/examples/PropOnProgress.js b/packages/website/storybook/1-components/Image/examples/PropOnProgress.js
new file mode 100644
index 000000000..85acbdf8b
--- /dev/null
+++ b/packages/website/storybook/1-components/Image/examples/PropOnProgress.js
@@ -0,0 +1,14 @@
+/**
+ * @flow
+ */
+
+import { createUncachedURI } from '../helpers';
+import NetworkImage from './NetworkImage';
+import React from 'react';
+import sources from '../sources';
+
+const ImageOnProgressExample = () => (
+
+);
+
+export default ImageOnProgressExample;
diff --git a/packages/website/storybook/1-components/Image/examples/PropSource.js b/packages/website/storybook/1-components/Image/examples/PropSource.js
index 43995f50e..70608eac3 100644
--- a/packages/website/storybook/1-components/Image/examples/PropSource.js
+++ b/packages/website/storybook/1-components/Image/examples/PropSource.js
@@ -36,6 +36,20 @@ const ImageSourceExample = () => (
+
+
+ WebP
+
+
+
+ Dynamic (POST)
+
+
+
+ Redirect
+
+
+
);
diff --git a/packages/website/storybook/1-components/Image/sources/index.js b/packages/website/storybook/1-components/Image/sources/index.js
index 254edc8e8..dd6307568 100644
--- a/packages/website/storybook/1-components/Image/sources/index.js
+++ b/packages/website/storybook/1-components/Image/sources/index.js
@@ -44,7 +44,18 @@ const sources = {
},
dataSvg,
dataBase64Png,
- dataBase64Svg
+ dataBase64Svg,
+ webP: {
+ uri: 'https://www.gstatic.com/webp/gallery/4.sm.webp'
+ },
+ dynamic: {
+ uri: 'https://chart.googleapis.com/chart',
+ method: 'POST',
+ body: 'cht=lc&chtt=Test&chs=300x200&chxt=x&chd=t:40,20,50,20,100'
+ },
+ redirect: {
+ uri: 'https://twitter.com/twitter/profile_image?size=original'
+ }
};
export default sources;