Skip to content

Commit 9f0ba5e

Browse files
authored
refactor sdk (#3)
Additional props for loading and error has been added for better error handling and rendering
1 parent 263491a commit 9f0ba5e

File tree

10 files changed

+314
-88
lines changed

10 files changed

+314
-88
lines changed

README.md

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
A simple reactjs library to resize image on fly.
44

5+
## What is assetcrush ?
6+
7+
[assetcrush](https://assetcrush.com/) is an image resize service for on the fly dynamic resize. If
8+
your users are uploading images of various sizes and then you are
9+
consuming those images on different devices then this service is ideal
10+
for your use. Since every device has different dimensions our sdk will
11+
make sure to get the rightly resized image for you via assetcrush resize
12+
service.
13+
514
### Installation
615

716
```
@@ -57,6 +66,25 @@ export default App;
5766

5867
</code>
5968

69+
70+
### Props
71+
72+
| Prop | Type | Required | Note |
73+
| -------- | -------- | -------- | --------------- |
74+
| `url` | `string` | yes | image url |
75+
| `width` | `string` | no | width of image |
76+
| `height` | `string` | no | height of image |
77+
| `animated` | `bool` | no | image fade in effect |
78+
| `reloadIconColor` | `string` | no | color of reload icon if image fails |
79+
| `spinnerIcon` | `string` | no | spinner component icon while image loads |
80+
| `spinnerColor` | `string` | no | color of spinner |
81+
| `hideSpinner` | `bool` | no | render spinner while image loads or not |
82+
| `onError` | `func` | no | if image fails to load (returns error details) |
83+
| `onLoad` | `func` | no | if image loads successfully (returns image headers) |
84+
85+
Any additional props are passed down to underlying `<img />` element.
86+
87+
6088
### ImageCrushAdoptive
6189

6290
Its the more adoptive method to use you don't need to pass width and height it will inherit width and height from outermost wrapped div
@@ -87,54 +115,34 @@ export default App;
87115

88116
| Prop | Type | Required | Note |
89117
| -------- | -------- | -------- | --------------- |
118+
| `backgroundColor` | `string` | no | color of background layer of image |
119+
| `debounce` | `number` | no | image layout handler debounce |
120+
| `wrapperStyle` | `object` | no | style object for outermost div |
90121
| `url` | `string` | yes | image url |
91122
| `width` | `string` | no | width of image |
92123
| `height` | `string` | no | height of image |
124+
| `animated` | `bool` | no | image fade in effect |
125+
| `reloadIconColor` | `string` | no | color of reload icon if image fails |
126+
| `spinnerIcon` | `string` | no | spinner component icon while image loads |
127+
| `spinnerColor` | `string` | no | color of spinner |
128+
| `hideSpinner` | `bool` | no | render spinner while image loads or not |
129+
| `onError` | `func` | no | if image fails to load (returns error details) |
130+
| `onLoad` | `func` | no | if image loads successfully (returns image headers) |
93131

94-
Any additional props are passed down to underlying `<img />` element.
132+
Any additional props are passed down to underlying `<img />` component.
95133

96134
## Run example
97135

98136
```javascript
99137
git clone https://github.com/assetcrush/reactjs-sdk.git
100138
cd reactjs-sdk/example
101139
yarn install # or npm install
102-
```
103-
104-
## Adoptive resize sample
105140

106-
Adoptive method to use this sdk is wrapping it inside div and using ref for responsive width and height
107-
108-
<code>
109-
110-
```javascript
111-
import React, { useRef } from "react";
112-
import { ImageCrush } from "@assetcrush/reactjs-sdk";
113-
114-
const testUrl =
115-
"https://cdn.pixabay.com/photo/2021/12/11/07/59/hotel-6862159__340.jpg";
116-
117-
function App() {
118-
const ref = useRef();
119-
120-
return (
121-
<div ref={useRef()} style={{ width: 50, height: 50 }}>
122-
<ImageCrush
123-
url={testUrl}
124-
width={ref.current?.clientWidth}
125-
height={ref.current?.clientHeight}
126-
/>
127-
</div>
128-
);
129-
}
130141

131142
export default App;
132143
```
133144

134-
</code>
135-
136145
## to run with npm
137-
138146
```javascript
139147
npm start
140148
```
@@ -144,6 +152,10 @@ npm start
144152
```javascript
145153
yarn start
146154
```
155+
## Testing with Jest
156+
157+
Make sure to add `jest.useFakeTimers();` to your test file.
158+
See [Stackoverflow](https://stackoverflow.com/questions/50793885/referenceerror-you-are-trying-to-import-a-file-after-the-jest-environment-has) post and [jest timer mocks](https://jestjs.io/docs/timer-mocks)
147159

148160
## Seeing issues or any feedback or feature suggest ?
149161

example/src/App.css

Lines changed: 0 additions & 38 deletions
This file was deleted.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { useCallback, useEffect, useRef } from "react";
2+
import "./styles.css";
3+
4+
export const ErrorRender = ({
5+
reloadIconColor = "gray",
6+
handleRetry = () => {},
7+
icon: Icon,
8+
height,
9+
}) => {
10+
const handler = useRef();
11+
12+
const _handleRetry = useCallback(() => {
13+
clearTimeout(handler.current);
14+
15+
handler.current = setTimeout(handleRetry, 300);
16+
}, [handleRetry]);
17+
18+
if (Icon) return <div onClick={_handleRetry}>{Icon}</div>;
19+
return (
20+
<span
21+
style={{ color: reloadIconColor, fontSize: height / 2 }}
22+
onClick={_handleRetry}
23+
className="assetcrush-reload"
24+
>
25+
&#x21bb;
26+
</span>
27+
);
28+
};
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.assetcrush-reload {
2+
font-family: Lucida Sans Unicode;
3+
cursor: pointer;
4+
font-size: '100%';
5+
width: '100%';
6+
height: '100%';
7+
font-weight: bold;
8+
}

example/src/modules/ImageCrush.js

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,91 @@
1-
import React, { memo, useEffect, useState } from "react";
1+
import React, { memo, useCallback, useEffect, useState } from "react";
2+
import { ErrorRender } from "./ErrorRender/ErrorRender";
23
import { getKey } from "./key";
4+
import { Spinner } from "./Spinner/Spinner";
5+
import "./styles.css";
36

47
const baseServiceUrl = "https://service.assetcrush.com";
58

6-
const ImageCrush = ({ url, width, height, ...props }) => {
9+
const ImageCrush = ({
10+
animated = true,
11+
reloadIconColor,
12+
reloadIcon,
13+
spinnerIcon,
14+
spinnerColor = "#fff",
15+
hideSpinner = false,
16+
onError = () => null,
17+
onLoad = () => null,
18+
url,
19+
width,
20+
height,
21+
acEnv = "production",
22+
...props
23+
}) => {
724
const [image, setImage] = useState("");
25+
const [isError, setIsError] = useState(false);
826

9-
useEffect(() => {
10-
if (!url) return;
27+
const handleError = useCallback(
28+
(e) => {
29+
onError(e);
30+
setIsError(true);
31+
},
32+
[setIsError]
33+
);
34+
35+
const fetchImage = useCallback(() => {
36+
setIsError(false);
1137

1238
const _width = width || "auto";
1339
const _height = height || "auto";
1440
const imageUrl = `${baseServiceUrl}?width=${_width}&height=${_height}&original_uri=${encodeURIComponent(
1541
url
1642
)}`;
1743
const key = getKey();
44+
let headers = {};
1845

19-
fetch(imageUrl, { headers: { "assetcrush-key": key } })
20-
.then((r) => r.blob())
46+
fetch(imageUrl, { headers: { "assetcrush-key": key, "ac-env": acEnv } })
47+
.then((r) => {
48+
headers = r.headers;
49+
return r.blob();
50+
})
2151
.then((d) => {
2252
if (typeof window === undefined) return;
53+
if (!d.type.includes("image")) {
54+
throw new Error("Object is not a valid image");
55+
}
2356
setImage(window.URL.createObjectURL(d));
24-
});
57+
return Promise.resolve();
58+
})
59+
.then(() => onLoad(headers))
60+
.catch(handleError);
61+
}, [setIsError]);
62+
63+
64+
const handleRetry = useCallback(() => {
65+
fetchImage();
66+
}, [fetchImage]);
67+
68+
useEffect(() => {
69+
if (!url) return;
70+
fetchImage();
2571
}, [url, width, height]);
2672

27-
if (!image) return null;
73+
if (isError)
74+
return (
75+
<ErrorRender
76+
width={width}
77+
height={height}
78+
reloadIconColor={reloadIconColor}
79+
icon={reloadIcon}
80+
handleRetry={handleRetry}
81+
/>
82+
);
83+
if (!image)
84+
return (
85+
<>{!hideSpinner && <Spinner icon={spinnerIcon} color={spinnerColor} />}</>
86+
);
2887

29-
return <img src={image} {...props} />;
88+
return <img className={animated && "assetcrush-fade-in-Image"} src={image} {...props} />;
3089
};
3190

3291
export default memo(ImageCrush);

example/src/modules/ImageCrushAdoptive.js

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,46 @@
11
import React, {
22
memo,
3+
useCallback,
34
useEffect,
45
useLayoutEffect,
56
useRef,
67
useState,
78
} from "react";
89
import ImageCrush from "./ImageCrush";
910

10-
const ImageCrushAdoptive = ({ url }) => {
11-
const [width, setWidth] = useState("");
12-
const [height, setHeight] = useState("");
11+
const styles = (backgroundColor) => ({
12+
wrapperStyle: { height: "inherit", width: "inherit", background: backgroundColor, display:'flex', alignItems:'center', justifyContent: 'center' }
13+
})
14+
15+
const ImageCrushAdoptive = ({ backgroundColor = '#f9f9f9', url, debounce = 100, wrapperStyle, ...restProps }) => {
16+
const [width, setWidth] = useState(0);
17+
const [height, setHeight] = useState(0);
18+
const [currentHeight, setCurrentHeight] = useState(0)
1319
const ref = useRef();
20+
const _wrapperStyle = wrapperStyle || styles(backgroundColor).wrapperStyle;
21+
1422

15-
useLayoutEffect(() => {
23+
const onLayout = useCallback(() => {
1624
const _width = ref?.current?.clientWidth;
1725
const _height = ref?.current?.clientHeight;
26+
setCurrentHeight(_height)
27+
if (_width > width || _height > height) {
28+
setWidth(_width);
29+
setHeight(_height);
30+
}
31+
}, [setWidth,setHeight, setCurrentHeight])
1832

19-
setWidth(_width);
20-
setHeight(_height);
33+
useLayoutEffect(() => {
34+
const handler = setTimeout(onLayout, debounce);
35+
36+
return () => clearTimeout(handler);
2137
}, []);
2238

39+
2340
return (
24-
<div style={{ height: "inherit", width: "inherit" }} ref={ref}>
25-
{width && height && (
26-
<ImageCrush url={url} width={width} height={height} />
41+
<div style={_wrapperStyle} ref={ref}>
42+
{!!width && !!height && (
43+
<ImageCrush url={url} width={width} height={currentHeight} {...restProps} />
2744
)}
2845
</div>
2946
);
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react";
2+
import "./styles.css";
3+
4+
export const Spinner = ({icon:Icon, backgroundColor = 'gray'}) => {
5+
if (Icon) return Icon;
6+
return (
7+
<>
8+
<style>
9+
{`
10+
.assetcrush-lds-roller div:after {
11+
content: " ";
12+
display: block;
13+
position: absolute;
14+
width: 7px;
15+
height: 7px;
16+
border-radius: 50%;
17+
background: ${backgroundColor};
18+
margin: -4px 0 0 -4px;
19+
}
20+
`}
21+
</style>
22+
<div className="assetcrush-lds-roller">
23+
<div></div>
24+
<div></div>
25+
<div></div>
26+
<div></div>
27+
<div></div>
28+
<div></div>
29+
<div></div>
30+
<div></div>
31+
</div>
32+
</>
33+
);
34+
};

0 commit comments

Comments
 (0)