Skip to content

Commit cf453ad

Browse files
committed
feat: add image dimensions tracking and enhance export options with metadata support
1 parent 65a6f3e commit cf453ad

File tree

9 files changed

+135
-51
lines changed

9 files changed

+135
-51
lines changed

examples/react-example/src/index.tsx

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import ReactDOM from "react-dom"
88
function App() {
99
const [open, setOpen] = React.useState(true)
1010
const [editorProps, setEditorProps] =
11-
React.useState<ImageKitEditorProps<{ requireSignedUrl: boolean }>>()
11+
React.useState<
12+
ImageKitEditorProps<{ requireSignedUrl: boolean; fileName: string }>
13+
>()
1214
const ref = React.useRef<ImageKitEditorRef>(null)
1315

1416
/**
@@ -28,18 +30,21 @@ function App() {
2830
url: "https://ik.imagekit.io/v3sxk1svj/white%20BMW%20car%20on%20street.jpg",
2931
metadata: {
3032
requireSignedUrl: false,
33+
fileName: "white BMW car on street.jpg",
3134
},
3235
},
3336
{
3437
url: "https://ik.imagekit.io/v3sxk1svj/Young%20Living%20Patchouili%20bot....jpg",
3538
metadata: {
3639
requireSignedUrl: false,
40+
fileName: "Young Living Patchouili bot.jpg",
3741
},
3842
},
3943
{
4044
url: "https://ik.imagekit.io/v3sxk1svj/brown%20bear%20plush%20toy%20on%20whi....jpg?updatedAt=1760432666859",
4145
metadata: {
4246
requireSignedUrl: false,
47+
fileName: "brown bear plush toy on white.jpg",
4348
},
4449
},
4550
// ...Array.from({ length: 10000 }).map((_, i) => ({
@@ -57,26 +62,26 @@ function App() {
5762
label: "Export",
5863
icon: <Icon boxSize={"5"} as={PiDownload} />,
5964
isVisible: true,
60-
onClick: (images) => {
61-
console.log(images)
65+
onClick: (images, currentImage) => {
66+
console.log(images, currentImage)
6267
},
6368
},
64-
{
65-
type: "menu",
66-
label: "Export",
67-
icon: <Icon boxSize={"5"} as={PiDownload} />,
68-
isVisible: true,
69-
options: [
70-
{
71-
label: "Export",
72-
icon: <Icon boxSize={"5"} as={PiDownload} />,
73-
isVisible: true,
74-
onClick: (images) => {
75-
console.log(images)
76-
},
77-
},
78-
],
79-
},
69+
// {
70+
// type: "menu",
71+
// label: "Export",
72+
// icon: <Icon boxSize={"5"} as={PiDownload} />,
73+
// isVisible: true,
74+
// options: [
75+
// {
76+
// label: "Export",
77+
// icon: <Icon boxSize={"5"} as={PiDownload} />,
78+
// isVisible: true,
79+
// onClick: (images) => {
80+
// console.log(images)
81+
// },
82+
// },
83+
// ],
84+
// },
8085
],
8186
signer: async (request) => {
8287
console.log(request)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ 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"
87
import {
98
type FileElement,
109
type FocusObjects,
@@ -25,7 +24,7 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
2524
initialImages?: Array<string | FileElement<Metadata>>
2625
signer?: Signer<Metadata>
2726
onAddImage?: () => void
28-
exportOptions?: HeaderProps["exportOptions"]
27+
exportOptions?: HeaderProps<Metadata>["exportOptions"]
2928
focusObjects?: ReadonlyArray<FocusObjects>
3029
onClose: (args: { dirty: boolean; destroy: () => void }) => void
3130
}

packages/imagekit-editor-dev/src/components/RetryableImage.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "@chakra-ui/react"
1212
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
1313
import { useVisibility } from "../hooks/useVisibility"
14+
import { useEditorStore } from "../store"
1415

1516
export interface RetryableImageProps extends ImageProps {
1617
maxRetries?: number
@@ -144,7 +145,8 @@ export default function RetryableImage(props: RetryableImageProps) {
144145

145146
const overlayActive = !!externalLoading || loading
146147

147-
const handleVisibleLoad = () => {
148+
const handleVisibleLoad = (event: React.SyntheticEvent<HTMLImageElement>) => {
149+
imgProps?.onLoad?.(event)
148150
setLoading(false)
149151
setError(null)
150152
lastSuccessBaseRef.current = currentSrcBase

packages/imagekit-editor-dev/src/components/editor/ActionBar.tsx

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import { PiGridFour } from "@react-icons/all-files/pi/PiGridFour"
1818
import { PiImageSquare } from "@react-icons/all-files/pi/PiImageSquare"
1919
import { PiListBullets } from "@react-icons/all-files/pi/PiListBullets"
20-
import { type FC, useEffect, useState } from "react"
20+
import { type FC, useMemo } from "react"
2121
import { useEditorStore } from "../../store"
2222

2323
interface ActionBarProps {
@@ -33,25 +33,19 @@ export const ActionBar: FC<ActionBarProps> = ({
3333
gridImageSize,
3434
setGridImageSize,
3535
}) => {
36-
const { currentImage, showOriginal, setShowOriginal } = useEditorStore()
36+
const {
37+
currentImage,
38+
imageList,
39+
originalImageList,
40+
showOriginal,
41+
setShowOriginal,
42+
} = useEditorStore()
3743

38-
const [imageDimensions, setImageDimensions] = useState<{
39-
width: number
40-
height: number
41-
} | null>(null)
42-
43-
useEffect(() => {
44-
if (currentImage) {
45-
const img = new Image()
46-
img.onload = () => {
47-
setImageDimensions({
48-
width: img.naturalWidth,
49-
height: img.naturalHeight,
50-
})
51-
}
52-
img.src = currentImage
53-
}
54-
}, [currentImage])
44+
const imageDimensions = useMemo(() => {
45+
const idx = imageList.findIndex((img) => img === currentImage)
46+
if (idx === -1) return null
47+
return originalImageList[idx].imageDimensions
48+
}, [currentImage, imageList, originalImageList])
5549

5650
return (
5751
<Box

packages/imagekit-editor-dev/src/components/editor/GridView.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const GridView: FC<GridViewProps> = ({ imageSize, onAddImage }) => {
2727
originalImageList,
2828
signingImages,
2929
removeImage,
30+
setImageDimensions,
3031
} = useEditorStore()
3132
return (
3233
<Flex
@@ -157,6 +158,12 @@ export const GridView: FC<GridViewProps> = ({ imageSize, onAddImage }) => {
157158
</Center>
158159
}
159160
isLoading={isSigning}
161+
onLoad={(event) => {
162+
setImageDimensions(originalImageList[index]!.url, {
163+
width: event.currentTarget.naturalWidth,
164+
height: event.currentTarget.naturalHeight,
165+
})
166+
}}
160167
/>
161168
</Box>
162169
)}

packages/imagekit-editor-dev/src/components/editor/ListView.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export const ListView: FC<ListViewProps> = ({ onAddImage }) => {
1515
imageList,
1616
originalImageList,
1717
signingImages,
18+
setImageDimensions,
1819
_internalState,
1920
} = useEditorStore()
2021

@@ -53,6 +54,16 @@ export const ListView: FC<ListViewProps> = ({ onAddImage }) => {
5354
const originalUrl = originalImageList[idx]?.url
5455
return originalUrl ? signingImages[originalUrl] : false
5556
})()}
57+
onLoad={(event) => {
58+
console.log(event)
59+
if (!currentImage) return
60+
const idx = imageList.findIndex((img) => img === currentImage)
61+
if (idx === -1) return
62+
setImageDimensions(originalImageList[idx]!.url, {
63+
width: event.currentTarget.naturalWidth,
64+
height: event.currentTarget.naturalHeight,
65+
})
66+
}}
5667
/>
5768
</Flex>
5869
</Flex>

packages/imagekit-editor-dev/src/components/header/index.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,46 @@ import { PiImageSquare } from "@react-icons/all-files/pi/PiImageSquare"
1414
import { PiImagesSquare } from "@react-icons/all-files/pi/PiImagesSquare"
1515
import { PiX } from "@react-icons/all-files/pi/PiX"
1616
import React, { useMemo } from "react"
17-
import { useEditorStore } from "../../store"
17+
import {
18+
type FileElement,
19+
type RequiredMetadata,
20+
useEditorStore,
21+
} from "../../store"
1822

19-
interface ExportOptionButton {
23+
interface ExportOptionButton<
24+
Metadata extends RequiredMetadata = RequiredMetadata,
25+
> {
2026
type: "button"
2127
label: string
2228
icon?: React.ReactElement
2329
isVisible: boolean | ((images: string[], currentImage?: string) => boolean)
24-
onClick: (images: string[], currentImage?: string) => void
30+
onClick: (
31+
images: { url: string; file: FileElement<Metadata> }[],
32+
currentImage?: { url: string; file: FileElement<Metadata> },
33+
) => void
2534
}
2635

27-
interface ExportOptionMenu {
36+
interface ExportOptionMenu<
37+
Metadata extends RequiredMetadata = RequiredMetadata,
38+
> {
2839
type: "menu"
2940
label: string
3041
icon?: React.ReactElement
3142
isVisible: boolean | ((images: string[], currentImage?: string) => boolean)
32-
options: Array<Omit<ExportOptionButton, "type">>
43+
options: Array<Omit<ExportOptionButton<Metadata>, "type">>
3344
}
3445

35-
export interface HeaderProps {
46+
export interface HeaderProps<
47+
Metadata extends RequiredMetadata = RequiredMetadata,
48+
> {
3649
onClose: () => void
37-
exportOptions?: Array<ExportOptionButton | ExportOptionMenu>
50+
exportOptions?: Array<
51+
ExportOptionButton<Metadata> | ExportOptionMenu<Metadata>
52+
>
3853
}
3954

4055
export const Header = ({ onClose, exportOptions }: HeaderProps) => {
41-
const { imageList, currentImage } = useEditorStore()
56+
const { imageList, originalImageList, currentImage } = useEditorStore()
4257

4358
const headerText = useMemo(() => {
4459
if (imageList.length === 1) {
@@ -92,7 +107,19 @@ export const Header = ({ onClose, exportOptions }: HeaderProps) => {
92107
borderRadius="0"
93108
px="8"
94109
size="sm"
95-
onClick={() => exportOption.onClick(imageList, currentImage)}
110+
onClick={() => {
111+
const images = imageList.map((image, index) => ({
112+
url: image,
113+
file: originalImageList[index],
114+
}))
115+
const cImage = images.find(
116+
(image) => image.url === currentImage,
117+
)
118+
exportOption.onClick(images, {
119+
url: cImage!.url,
120+
file: cImage!.file,
121+
})
122+
}}
96123
>
97124
{exportOption.label}
98125
</Button>
@@ -121,7 +148,19 @@ export const Header = ({ onClose, exportOptions }: HeaderProps) => {
121148
.map((option) => (
122149
<MenuItem
123150
key={`export-menu-option-${option.label}`}
124-
onClick={() => option.onClick(imageList, currentImage)}
151+
onClick={() => {
152+
const images = imageList.map((image, index) => ({
153+
url: image,
154+
file: originalImageList[index],
155+
}))
156+
const cImage = images.find(
157+
(image) => image.url === currentImage,
158+
)
159+
option.onClick(images, {
160+
url: cImage!.url,
161+
file: cImage!.file,
162+
})
163+
}}
125164
>
126165
{option.label}
127166
</MenuItem>

packages/imagekit-editor-dev/src/components/toolbar/toolbar.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const Toolbar: FC<ToolbarProps> = ({ onAddImage, onSelectImage }) => {
2121
signingImages,
2222
setCurrentImage,
2323
removeImage,
24+
setImageDimensions,
2425
_internalState,
2526
} = useEditorStore()
2627

@@ -204,6 +205,12 @@ export const Toolbar: FC<ToolbarProps> = ({ onAddImage, onSelectImage }) => {
204205
</Center>
205206
}
206207
isLoading={isSigning}
208+
onLoad={(event) => {
209+
setImageDimensions(originalImageList[index]!.url, {
210+
width: event.currentTarget.naturalWidth,
211+
height: event.currentTarget.naturalHeight,
212+
})
213+
}}
207214
/>
208215
</Box>
209216
)}

packages/imagekit-editor-dev/src/store.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface FileElement<
2828
> {
2929
url: string
3030
metadata: Metadata
31+
imageDimensions: { width: number; height: number } | null
3132
}
3233

3334
export interface SignerRequest<
@@ -90,6 +91,10 @@ export type EditorActions<
9091
}) => void
9192
destroy: () => void
9293
setCurrentImage: (imageSrc: string | undefined) => void
94+
setImageDimensions: (
95+
imageSrc: string,
96+
dimensions: { width: number; height: number } | null,
97+
) => void
9398
addImage: (imageSrc: string | FileElement<Metadata>) => void
9499
addImages: (imageSrcs: Array<string | FileElement<Metadata>>) => void
95100
removeImage: (imageSrc: string) => void
@@ -137,6 +142,7 @@ function normalizeImage<Metadata extends RequiredMetadata = RequiredMetadata>(
137142
return {
138143
url: image,
139144
metadata: { requireSignedUrl: false } as Metadata,
145+
imageDimensions: null,
140146
}
141147
}
142148
return {
@@ -147,6 +153,7 @@ function normalizeImage<Metadata extends RequiredMetadata = RequiredMetadata>(
147153
requireSignedUrl: image.metadata.requireSignedUrl ?? false,
148154
}
149155
: ({ requireSignedUrl: false } as Metadata),
156+
imageDimensions: null,
150157
}
151158
}
152159

@@ -202,6 +209,19 @@ const useEditorStore = create<EditorState & EditorActions>()(
202209
set({ currentImage: imageSrc })
203210
},
204211

212+
setImageDimensions: (imageSrc, imageDimensions) => {
213+
set((state) => {
214+
console.log(imageSrc, state.originalImageList)
215+
const index = state.originalImageList.findIndex(
216+
(img) => img.url === imageSrc,
217+
)
218+
if (index === -1) return state
219+
const updatedImageList = [...state.originalImageList]
220+
updatedImageList[index].imageDimensions = imageDimensions
221+
return { originalImageList: updatedImageList }
222+
})
223+
},
224+
205225
addImage: (imageSrc) => {
206226
const img = normalizeImage(imageSrc)
207227
if (!get().originalImageList.some((i) => i.url === img.url)) {

0 commit comments

Comments
 (0)