Skip to content

Commit 7d854ad

Browse files
committed
feat: add object detection focus options and coordinate-based positioning for image transformations
1 parent 13a3202 commit 7d854ad

File tree

6 files changed

+330
-43
lines changed

6 files changed

+330
-43
lines changed

examples/react-example/src/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ function App() {
3636
requireSignedUrl: false,
3737
},
3838
},
39+
{
40+
url: "https://ik.imagekit.io/v3sxk1svj/brown%20bear%20plush%20toy%20on%20whi....jpg?updatedAt=1760432666859",
41+
metadata: {
42+
requireSignedUrl: false,
43+
},
44+
},
3945
// ...Array.from({ length: 10000 }).map((_, i) => ({
4046
// url: `https://ik.imagekit.io/v3sxk1svj/placeholder.jpg?updatedAt=${Date.now()}&v=${i}`,
4147
// metadata: {

packages/imagekit-editor-dev/src/ImageKitEditor.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import merge from "lodash/merge"
44
import React, { forwardRef, useImperativeHandle } from "react"
55
import { EditorLayout, EditorWrapper } from "./components/editor"
66
import type { HeaderProps } from "./components/header"
7+
import type { DEFAULT_FOCUS_OBJECTS } from "./schema"
78
import {
89
type FileElement,
910
type RequiredMetadata,
@@ -24,15 +25,17 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
2425
signer?: Signer<Metadata>
2526
onAddImage?: () => void
2627
exportOptions?: HeaderProps["exportOptions"]
27-
28+
focusObjects?: ReadonlyArray<
29+
(typeof DEFAULT_FOCUS_OBJECTS)[number] | (string & {})
30+
>
2831
onClose: (args: { dirty: boolean; destroy: () => void }) => void
2932
}
3033

3134
function ImageKitEditorImpl<M extends RequiredMetadata>(
3235
props: EditorProps<M>,
3336
ref: React.Ref<ImageKitEditorRef>,
3437
) {
35-
const { theme, initialImages, signer } = props
38+
const { theme, initialImages, signer, focusObjects } = props
3639
const {
3740
addImage,
3841
addImages,
@@ -62,8 +65,9 @@ function ImageKitEditorImpl<M extends RequiredMetadata>(
6265
initialize({
6366
imageList: initialImages,
6467
signer,
68+
focusObjects,
6569
})
66-
}, [initialImages, signer, initialize])
70+
}, [initialImages, signer, focusObjects, initialize])
6771

6872
useImperativeHandle(
6973
ref,

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

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ import { PiArrowLeft } from "@react-icons/all-files/pi/PiArrowLeft"
3737
import { PiCaretDown } from "@react-icons/all-files/pi/PiCaretDown"
3838
import { PiInfo } from "@react-icons/all-files/pi/PiInfo"
3939
import { PiX } from "@react-icons/all-files/pi/PiX"
40+
import startCase from "lodash/startCase"
4041
import { useEffect, useMemo } from "react"
4142
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
4243
import Select from "react-select"
4344
import CreateableSelect from "react-select/creatable"
4445
import { z } from "zod/v3"
4546
import type { TransformationField } from "../../schema"
46-
import { transformationSchema } from "../../schema"
47+
import { DEFAULT_FOCUS_OBJECTS, transformationSchema } from "../../schema"
4748
import { useEditorStore } from "../../store"
4849
import { isStepAligned } from "../../utils"
4950
import AnchorField from "../common/AnchorField"
@@ -61,6 +62,7 @@ export const TransformationConfigSidebar: React.FC = () => {
6162
addTransformation,
6263
updateTransformation,
6364
imageList,
65+
focusObjects,
6466
_setSidebarState,
6567
_internalState,
6668
_setTransformationToEdit,
@@ -296,40 +298,74 @@ export const TransformationConfigSidebar: React.FC = () => {
296298
<Controller
297299
name={field.name}
298300
control={control}
299-
render={({ field: controllerField }) => (
300-
<Select
301-
id={field.name}
302-
placeholder="Select"
303-
menuPlacement="auto"
304-
options={field.fieldProps?.options?.map((option) => ({
305-
value: option.value,
306-
label: option.label,
307-
}))}
308-
value={field.fieldProps?.options?.find(
309-
(option) => option.value === controllerField.value,
310-
)}
311-
onChange={(selectedOption) =>
312-
controllerField.onChange(selectedOption?.value)
313-
}
314-
onBlur={controllerField.onBlur}
315-
styles={{
316-
control: (base) => ({
317-
...base,
318-
fontSize: "12px",
319-
minHeight: "32px",
320-
borderColor: "#E2E8F0",
321-
}),
322-
menu: (base) => ({
323-
...base,
324-
zIndex: 10,
325-
}),
326-
option: (base) => ({
327-
...base,
328-
fontSize: "12px",
329-
}),
330-
}}
331-
/>
332-
)}
301+
render={({ field: controllerField }) => {
302+
// For focusObject field, use focusObjects from store or default list
303+
const selectOptions =
304+
field.name === "focusObject"
305+
? (focusObjects || DEFAULT_FOCUS_OBJECTS).map(
306+
(obj) => ({
307+
value: obj,
308+
label: startCase(obj),
309+
}),
310+
)
311+
: field.fieldProps?.options?.map((option) => ({
312+
value: option.value,
313+
label: option.label,
314+
}))
315+
316+
const isCreatable = field.fieldProps?.isCreatable === true
317+
const SelectComponent = isCreatable
318+
? CreateableSelect
319+
: Select
320+
321+
// For creatable selects, find the value in options or create a custom one
322+
const selectedValue = isCreatable
323+
? selectOptions?.find(
324+
(option) => option.value === controllerField.value,
325+
) ||
326+
(controllerField.value
327+
? {
328+
value: controllerField.value as string,
329+
label: startCase(controllerField.value as string),
330+
}
331+
: null)
332+
: selectOptions?.find(
333+
(option) => option.value === controllerField.value,
334+
)
335+
336+
return (
337+
<SelectComponent
338+
id={field.name}
339+
formatCreateLabel={(inputValue) =>
340+
`Use "${inputValue}"`
341+
}
342+
placeholder="Select"
343+
menuPlacement="auto"
344+
options={selectOptions}
345+
value={selectedValue}
346+
onChange={(selectedOption) =>
347+
controllerField.onChange(selectedOption?.value)
348+
}
349+
onBlur={controllerField.onBlur}
350+
styles={{
351+
control: (base) => ({
352+
...base,
353+
fontSize: "12px",
354+
minHeight: "32px",
355+
borderColor: "#E2E8F0",
356+
}),
357+
menu: (base) => ({
358+
...base,
359+
zIndex: 10,
360+
}),
361+
option: (base) => ({
362+
...base,
363+
fontSize: "12px",
364+
}),
365+
}}
366+
/>
367+
)
368+
}}
333369
/>
334370
) : null}
335371
{field.fieldType === "select-creatable" ? (
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export type { ImageKitEditorProps, ImageKitEditorRef } from "./ImageKitEditor"
22
export { ImageKitEditor } from "./ImageKitEditor"
3+
export { DEFAULT_FOCUS_OBJECTS } from "./schema"
34
export type { FileElement, Signer } from "./store"

0 commit comments

Comments
 (0)