Skip to content

Commit 415b18a

Browse files
committed
Add Distortion support in base and overlay image
1 parent 7d21805 commit 415b18a

File tree

3 files changed

+444
-3
lines changed

3 files changed

+444
-3
lines changed
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import {
2+
Box,
3+
Flex,
4+
HStack,
5+
VStack,
6+
Icon,
7+
Text,
8+
Input,
9+
InputGroup,
10+
InputLeftElement,
11+
IconButton,
12+
useColorModeValue,
13+
Tooltip,
14+
} from "@chakra-ui/react";
15+
import type * as React from "react";
16+
import { useState, useEffect } from "react";
17+
import { RxArrowTopLeft } from "@react-icons/all-files/rx/RxArrowTopLeft";
18+
import { RxArrowTopRight } from "@react-icons/all-files/rx/RxArrowTopRight";
19+
import { RxArrowBottomRight } from "@react-icons/all-files/rx/RxArrowBottomRight";
20+
import { RxArrowBottomLeft } from "@react-icons/all-files/rx/RxArrowBottomLeft";
21+
import { FieldErrors } from "react-hook-form";
22+
23+
type DistorPerspectiveFieldProps = {
24+
name: string;
25+
id?: string;
26+
onChange: (value: PerspectiveObject) => void;
27+
errors?: FieldErrors<Record<string, unknown>>;
28+
value?: PerspectiveObject;
29+
};
30+
31+
export type PerspectiveObject = {
32+
x1: string;
33+
y1: string;
34+
x2: string;
35+
y2: string;
36+
x3: string;
37+
y3: string;
38+
x4: string;
39+
y4: string;
40+
};
41+
42+
export const DistortPerspectiveInput: React.FC<DistorPerspectiveFieldProps> = ({
43+
id,
44+
onChange,
45+
errors,
46+
name: propertyName,
47+
value,
48+
}) => {
49+
const [perspective, setPerspective] = useState<PerspectiveObject>(value ?? {
50+
x1: "",
51+
y1: "",
52+
x2: "",
53+
y2: "",
54+
x3: "",
55+
y3: "",
56+
x4: "",
57+
y4: "",
58+
});
59+
const errorRed = useColorModeValue("red.500", "red.300");
60+
const leftAccessoryBackground = useColorModeValue("gray.100", "gray.700");
61+
62+
function handleFieldChange(fieldName: string) {
63+
return (e: React.ChangeEvent<HTMLInputElement>) => {
64+
const val = e.target.value.trim();
65+
setPerspective((prev) => ({
66+
...prev,
67+
[fieldName]: val,
68+
}));
69+
};
70+
}
71+
72+
useEffect(() => {
73+
onChange(perspective);
74+
}, [perspective]);
75+
76+
return (
77+
<VStack as="fieldset" id={id} role="group" spacing={2} alignItems="stretch">
78+
<HStack spacing={0}>
79+
<InputGroup flex="1.52">
80+
<InputLeftElement
81+
pointerEvents="none"
82+
background={leftAccessoryBackground}
83+
borderLeftRadius="md"
84+
>
85+
<Icon as={RxArrowTopLeft} color="gray.500" />
86+
</InputLeftElement>
87+
<Input
88+
fontSize="sm"
89+
type="number"
90+
value={perspective.x1 ?? ""}
91+
placeholder="X1"
92+
isInvalid={!!errors?.[propertyName]?.x1}
93+
onChange={handleFieldChange("x1")}
94+
borderRightRadius={0}
95+
/>
96+
</InputGroup>
97+
<Input
98+
fontSize="sm"
99+
type="number"
100+
value={perspective.y1 ?? ""}
101+
placeholder="Y1"
102+
flex="1"
103+
isInvalid={!!errors?.[propertyName]?.y1}
104+
onChange={handleFieldChange("y1")}
105+
borderLeftRadius={0}
106+
borderLeft={0}
107+
paddingInlineStart="0.1em"
108+
/>
109+
<Text fontSize="xs" color={errorRed}>
110+
{[
111+
errors?.[propertyName]?.x1?.message,
112+
errors?.[propertyName]?.y1?.message,
113+
]
114+
.filter(Boolean)
115+
.join(". ")}
116+
</Text>
117+
</HStack>
118+
119+
<HStack spacing={0}>
120+
<InputGroup flex="1.52">
121+
<InputLeftElement
122+
pointerEvents="none"
123+
background={leftAccessoryBackground}
124+
borderLeftRadius="md"
125+
>
126+
<Icon as={RxArrowTopRight} color="gray.500" />
127+
</InputLeftElement>
128+
<Input
129+
fontSize="sm"
130+
type="number"
131+
value={perspective.x2 ?? ""}
132+
placeholder="X2"
133+
isInvalid={!!errors?.[propertyName]?.x2}
134+
onChange={handleFieldChange("x2")}
135+
borderRightRadius={0}
136+
/>
137+
</InputGroup>
138+
<Input
139+
fontSize="sm"
140+
type="number"
141+
value={perspective.y2 ?? ""}
142+
placeholder="Y2"
143+
flex="1"
144+
isInvalid={!!errors?.[propertyName]?.y2}
145+
onChange={handleFieldChange("y2")}
146+
borderLeftRadius={0}
147+
borderLeft={0}
148+
paddingInlineStart="0.1em"
149+
/>
150+
<Text fontSize="xs" color={errorRed}>
151+
{[
152+
errors?.[propertyName]?.x2?.message,
153+
errors?.[propertyName]?.y2?.message,
154+
]
155+
.filter(Boolean)
156+
.join(". ")}
157+
</Text>
158+
</HStack>
159+
160+
<HStack spacing={0}>
161+
<InputGroup flex="1.52">
162+
<InputLeftElement
163+
pointerEvents="none"
164+
background={leftAccessoryBackground}
165+
borderLeftRadius="md"
166+
>
167+
<Icon as={RxArrowBottomRight} color="gray.500" />
168+
</InputLeftElement>
169+
<Input
170+
fontSize="sm"
171+
type="number"
172+
value={perspective.x3 ?? ""}
173+
placeholder="X3"
174+
isInvalid={!!errors?.[propertyName]?.x3}
175+
onChange={handleFieldChange("x3")}
176+
borderRightRadius={0}
177+
/>
178+
</InputGroup>
179+
<Input
180+
fontSize="sm"
181+
type="number"
182+
value={perspective.y3 ?? ""}
183+
placeholder="Y3"
184+
flex="1"
185+
isInvalid={!!errors?.[propertyName]?.y3}
186+
onChange={handleFieldChange("y3")}
187+
borderLeftRadius={0}
188+
borderLeft={0}
189+
paddingInlineStart="0.1em"
190+
/>
191+
<Text fontSize="xs" color={errorRed}>
192+
{[
193+
errors?.[propertyName]?.x3?.message,
194+
errors?.[propertyName]?.y3?.message,
195+
]
196+
.filter(Boolean)
197+
.join(". ")}
198+
</Text>
199+
</HStack>
200+
201+
<HStack spacing={0}>
202+
<InputGroup flex="1.52">
203+
<InputLeftElement
204+
pointerEvents="none"
205+
background={leftAccessoryBackground}
206+
borderLeftRadius="md"
207+
>
208+
<Icon as={RxArrowBottomLeft} color="gray.500" />
209+
</InputLeftElement>
210+
<Input
211+
fontSize="sm"
212+
type="number"
213+
value={perspective.x4 ?? ""}
214+
placeholder="X4"
215+
isInvalid={!!errors?.[propertyName]?.x4}
216+
onChange={handleFieldChange("x4")}
217+
borderRightRadius={0}
218+
/>
219+
</InputGroup>
220+
<Input
221+
fontSize="sm"
222+
type="number"
223+
value={perspective.y4 ?? ""}
224+
placeholder="Y4"
225+
flex="1"
226+
isInvalid={!!errors?.[propertyName]?.y4}
227+
onChange={handleFieldChange("y4")}
228+
borderLeftRadius={0}
229+
borderLeft={0}
230+
paddingInlineStart="0.1em"
231+
/>
232+
<Text fontSize="xs" color={errorRed}>
233+
{[
234+
errors?.[propertyName]?.x4?.message,
235+
errors?.[propertyName]?.y4?.message,
236+
]
237+
.filter(Boolean)
238+
.join(". ")}
239+
</Text>
240+
</HStack>
241+
</VStack>
242+
);
243+
};
244+
245+
export default DistortPerspectiveInput;

packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ import { SidebarBody } from "./sidebar-body"
5656
import { SidebarFooter } from "./sidebar-footer"
5757
import { SidebarHeader } from "./sidebar-header"
5858
import { SidebarRoot } from "./sidebar-root"
59-
import PaddingInputField from "../common/PaddingInput"
59+
import PaddingInputField, { PaddingObject } from "../common/PaddingInput"
6060
import ZoomInput from "../common/ZoomInput"
61+
import DistortPerspectiveInput, { PerspectiveObject } from "../common/DistortPerspectiveInput"
6162

6263
export const TransformationConfigSidebar: React.FC = () => {
6364
const {
@@ -419,6 +420,7 @@ export const TransformationConfigSidebar: React.FC = () => {
419420
id={field.name}
420421
fontSize="sm"
421422
{...register(field.name)}
423+
{...(field.fieldProps ?? {})}
422424
/>
423425
) : null}
424426
{field.fieldType === "textarea" ? (
@@ -592,6 +594,18 @@ export const TransformationConfigSidebar: React.FC = () => {
592594
{...field.fieldProps}
593595
/>
594596
) : null}
597+
{field.fieldType === "distort-perspective-input" ? (
598+
<DistortPerspectiveInput
599+
onChange={(value) => {
600+
setValue(field.name, value)
601+
trigger(field.name)
602+
}}
603+
errors={errors}
604+
name={field.name}
605+
value={watch(field.name) as PerspectiveObject}
606+
{...field.fieldProps}
607+
/>
608+
) : null}
595609
<FormErrorMessage fontSize="sm">
596610
{String(
597611
errors[field.name as keyof typeof errors]?.message ?? "",

0 commit comments

Comments
 (0)