Skip to content

Commit 2b9e73f

Browse files
committed
feat(tool): new tool - images resizer
1 parent 6129cb6 commit 2b9e73f

File tree

4 files changed

+269
-14
lines changed

4 files changed

+269
-14
lines changed

components/Tools/Image/ImageResizer/index.tsx

+222-11
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,238 @@
11
import {
22
Input,
3-
InputLeftElement,
43
Box,
54
InputGroup,
6-
IconButton,
5+
Button,
6+
Image,
7+
Text,
8+
Flex,
9+
chakra,
710
} from "@chakra-ui/react";
811
import { FiUpload } from "react-icons/fi";
9-
import { useRef } from "react";
12+
import { useRef, useState, ChangeEvent } from "react";
13+
import { Formik, Form, Field, ErrorMessage } from "formik";
14+
import getAspectRatio from "@/utils/getAspectRatio";
15+
import Resizer from "react-image-file-resizer";
16+
17+
interface IImage {
18+
src?: string;
19+
alt?: string;
20+
width?: number;
21+
height?: number;
22+
aspectRatio?: number[];
23+
file?: File;
24+
}
1025

1126
const ImageResizer = (): JSX.Element => {
1227
const inputElRef = useRef<HTMLInputElement>(null);
28+
const ogImageRef = useRef<HTMLImageElement>(null);
29+
const resizedImageRef = useRef<HTMLImageElement>(null);
30+
const [ogImage, setOgImage] = useState<IImage>();
31+
const [resizedImage, setResizedImage] = useState<IImage>();
32+
33+
const handleFileChange = (e: ChangeEvent<HTMLInputElement>): void => {
34+
const fileList = e.target.files;
35+
if (!fileList) return;
36+
const file = fileList[0];
37+
setOgImage(prev => {
38+
if (prev) {
39+
return {
40+
...prev,
41+
src: URL.createObjectURL(file),
42+
};
43+
}
44+
return {
45+
src: URL.createObjectURL(file),
46+
alt: file.name,
47+
width: 0,
48+
height: 0,
49+
aspectRatio: [0, 0],
50+
file,
51+
};
52+
});
53+
console.log(file);
54+
};
55+
56+
const handleOgImageLoad = (): void => {
57+
if (!ogImageRef.current) return;
58+
const ogImage = ogImageRef.current;
59+
const { width, height } = ogImage;
60+
const aspectRatio: number[] = getAspectRatio(width, height);
61+
setOgImage(prev => ({ ...prev, width, height, aspectRatio }));
62+
};
63+
64+
const handleResizedImageLoad = (): void => {
65+
if (!resizedImageRef.current) return;
66+
const resizedImage = resizedImageRef.current;
67+
const { width, height } = resizedImage;
68+
const aspectRatio: number[] = getAspectRatio(width, height);
69+
setResizedImage(prev => ({ ...prev, width, height, aspectRatio }));
70+
};
71+
1372
return (
1473
<Box>
1574
<InputGroup>
16-
<InputLeftElement>
17-
<IconButton
18-
icon={<FiUpload />}
19-
aria-label="Upload image"
20-
onClick={() => inputElRef.current?.click()}
21-
/>
22-
<Input type="file" accept="image/*" hidden ref={inputElRef} />
23-
</InputLeftElement>
75+
<Button
76+
leftIcon={<FiUpload />}
77+
aria-label="Upload image"
78+
onClick={() => inputElRef.current?.click()}
79+
mb={4}
80+
>
81+
Upload Image
82+
</Button>
83+
<Input
84+
type="file"
85+
accept="image/*"
86+
multiple={false}
87+
hidden
88+
ref={inputElRef}
89+
onChange={handleFileChange}
90+
/>
2491
</InputGroup>
92+
<>
93+
{ogImage?.src && (
94+
<Image
95+
src={ogImage.src}
96+
alt={ogImage.alt}
97+
ref={ogImageRef}
98+
onLoad={handleOgImageLoad}
99+
/>
100+
)}
101+
{ogImage?.height && ogImage?.width && ogImage?.aspectRatio && (
102+
<>
103+
<Text
104+
mt={4}
105+
fontSize="xl"
106+
>{`Original Image Dimensions: ${ogImage.width}x${ogImage.height}`}</Text>
107+
<Text
108+
mt={4}
109+
fontSize="xl"
110+
>{`Original Image Aspect Ratio: ${ogImage.aspectRatio[0]}x${ogImage.aspectRatio[1]}`}</Text>
111+
</>
112+
)}
113+
</>
114+
115+
<Formik
116+
initialValues={{
117+
width: 500,
118+
height: 500,
119+
}}
120+
validate={values => {
121+
const errors: any = {};
122+
if (values.width <= 0) {
123+
errors.width = "Width must be greater than 0";
124+
}
125+
if (values.height <= 0) {
126+
errors.height = "Height must be greater than 0";
127+
}
128+
return errors;
129+
}}
130+
onSubmit={async (values, { setSubmitting }) => {
131+
Resizer.imageFileResizer(
132+
ogImage?.file as File,
133+
values.width,
134+
values.height,
135+
"JPEG",
136+
100,
137+
0,
138+
(file: any) => {
139+
setResizedImage(prev => {
140+
if (prev) {
141+
return {
142+
...prev,
143+
src: URL.createObjectURL(file),
144+
};
145+
}
146+
return {
147+
src: URL.createObjectURL(file),
148+
alt: file.name,
149+
width: 0,
150+
height: 0,
151+
aspectRatio: [0, 0],
152+
file,
153+
};
154+
});
155+
},
156+
"file"
157+
);
158+
159+
Resizer.imageFileResizer(
160+
ogImage?.file as File,
161+
values.width,
162+
values.height,
163+
"JPEG",
164+
100,
165+
0,
166+
(uri: any) => {
167+
console.log(uri);
168+
},
169+
"base64"
170+
);
171+
172+
setSubmitting(false);
173+
}}
174+
>
175+
{({ isSubmitting }) => (
176+
<Form>
177+
<Flex alignContent="center" verticalAlign="center" my={4}>
178+
<InputGroup lineHeight="40px">
179+
<Input
180+
as={Field}
181+
placeholder="Width"
182+
mr={2}
183+
type="number"
184+
name="width"
185+
/>
186+
<ErrorMessage name="width" />
187+
<chakra.span
188+
textAlign="center"
189+
display="inline-block"
190+
verticalAlign="middle"
191+
>
192+
x
193+
</chakra.span>
194+
<Input
195+
as={Field}
196+
placeholder="Height"
197+
ml={2}
198+
type="number"
199+
name="height"
200+
/>
201+
<ErrorMessage name="height" />
202+
</InputGroup>
203+
<Button ml={2} p={2} isLoading={isSubmitting} type="submit">
204+
Resize
205+
</Button>
206+
</Flex>
207+
</Form>
208+
)}
209+
</Formik>
210+
{resizedImage && (
211+
<>
212+
{resizedImage?.src && (
213+
<Image
214+
src={resizedImage.src}
215+
alt={resizedImage.alt}
216+
ref={resizedImageRef}
217+
onLoad={handleResizedImageLoad}
218+
/>
219+
)}
220+
{resizedImage?.height &&
221+
resizedImage?.width &&
222+
resizedImage?.aspectRatio && (
223+
<>
224+
<Text
225+
mt={4}
226+
fontSize="xl"
227+
>{`Resized Image Dimensions: ${resizedImage.width}x${resizedImage.height}`}</Text>
228+
<Text
229+
mt={4}
230+
fontSize="xl"
231+
>{`Resized Image Aspect Ratio: ${resizedImage.aspectRatio[0]}x${resizedImage.aspectRatio[1]}`}</Text>
232+
</>
233+
)}
234+
</>
235+
)}
25236
</Box>
26237
);
27238
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@types/file-saver": "^2.0.3",
2525
"codemirror": "^5.63.3",
2626
"file-saver": "^2.0.5",
27+
"formik": "^2.2.9",
2728
"framer-motion": "^4",
2829
"next": "^12.0.1",
2930
"next-pwa": "^5.4.0",

utils/getAspectRatio.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const gcd = (width: number, height: number): number => {
2+
return height === 0 ? width : gcd(height, width % height);
3+
};
4+
5+
const getAspectRatio = (width: number, height: number): number[] => {
6+
const g = gcd(width, height);
7+
return [width / g, height / g];
8+
};
9+
10+
export default getAspectRatio;

yarn.lock

+36-3
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,11 @@ deep-is@^0.1.3:
32123212
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
32133213
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
32143214

3215+
deepmerge@^2.1.1:
3216+
version "2.2.1"
3217+
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170"
3218+
integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==
3219+
32153220
deepmerge@^4.2.2:
32163221
version "4.2.2"
32173222
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@@ -3862,6 +3867,19 @@ foreach@^2.0.5:
38623867
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
38633868
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
38643869

3870+
formik@^2.2.9:
3871+
version "2.2.9"
3872+
resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.9.tgz#8594ba9c5e2e5cf1f42c5704128e119fc46232d0"
3873+
integrity sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==
3874+
dependencies:
3875+
deepmerge "^2.1.1"
3876+
hoist-non-react-statics "^3.3.0"
3877+
lodash "^4.17.21"
3878+
lodash-es "^4.17.21"
3879+
react-fast-compare "^2.0.1"
3880+
tiny-warning "^1.0.2"
3881+
tslib "^1.10.0"
3882+
38653883
framer-motion@^4:
38663884
version "4.1.17"
38673885
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-4.1.17.tgz#4029469252a62ea599902e5a92b537120cc89721"
@@ -4191,7 +4209,7 @@ hmac-drbg@^1.0.1:
41914209
minimalistic-assert "^1.0.0"
41924210
minimalistic-crypto-utils "^1.0.1"
41934211

4194-
hoist-non-react-statics@^3.3.1:
4212+
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1:
41954213
version "3.3.2"
41964214
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
41974215
integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -4780,6 +4798,11 @@ locate-path@^6.0.0:
47804798
dependencies:
47814799
p-locate "^5.0.0"
47824800

4801+
lodash-es@^4.17.21:
4802+
version "4.17.21"
4803+
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
4804+
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
4805+
47834806
lodash.debounce@^4.0.8:
47844807
version "4.0.8"
47854808
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
@@ -4815,7 +4838,7 @@ lodash.truncate@^4.4.2:
48154838
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
48164839
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
48174840

4818-
lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20:
4841+
lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
48194842
version "4.17.21"
48204843
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
48214844
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -5627,6 +5650,11 @@ react-fast-compare@3.2.0:
56275650
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
56285651
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
56295652

5653+
react-fast-compare@^2.0.1:
5654+
version "2.0.4"
5655+
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
5656+
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
5657+
56305658
react-focus-lock@2.5.0:
56315659
version "2.5.0"
56325660
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.0.tgz#12e3a3940e897c26e2c2a0408cd25ea3c99b3709"
@@ -6506,6 +6534,11 @@ tiny-invariant@^1.0.6:
65066534
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
65076535
integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
65086536

6537+
tiny-warning@^1.0.2:
6538+
version "1.0.3"
6539+
resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
6540+
integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
6541+
65096542
to-fast-properties@^2.0.0:
65106543
version "2.0.0"
65116544
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -6562,7 +6595,7 @@ tsconfig-paths@^3.11.0, tsconfig-paths@^3.9.0:
65626595
minimist "^1.2.0"
65636596
strip-bom "^3.0.0"
65646597

6565-
tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.3:
6598+
tslib@^1.0.0, tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.3:
65666599
version "1.14.1"
65676600
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
65686601
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==

0 commit comments

Comments
 (0)