1
1
import { Button , Divider , Flex , Select , Spacer , Text } from '@invoke-ai/ui-library' ;
2
2
import { useAppSelector } from 'app/store/storeHooks' ;
3
- import type { CropBox , Editor } from 'features/editImageModal/lib/editor' ;
3
+ import type { CropBox } from 'features/editImageModal/lib/editor' ;
4
+ import { closeEditImageModal , type EditImageModalState } from 'features/editImageModal/store' ;
4
5
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors' ;
5
6
import React , { useCallback , useEffect , useRef , useState } from 'react' ;
6
7
import { useUploadImageMutation } from 'services/api/endpoints/images' ;
7
8
8
9
type Props = {
9
- editor : Editor ;
10
+ editor : EditImageModalState [ 'editor' ] ;
11
+ onApplyCrop : EditImageModalState [ 'onApplyCrop' ] ;
12
+ onReady : EditImageModalState [ 'onReady' ] ;
10
13
} ;
11
14
12
15
const CROP_ASPECT_RATIO_MAP : Record < string , number > = {
@@ -19,7 +22,7 @@ const CROP_ASPECT_RATIO_MAP: Record<string, number> = {
19
22
'9:16' : 9 / 16 ,
20
23
} ;
21
24
22
- export const getAspectRatioString = ( ratio : number | null ) => {
25
+ const getAspectRatioString = ( ratio : number | null ) => {
23
26
if ( ! ratio ) {
24
27
return 'free' ;
25
28
}
@@ -32,53 +35,32 @@ export const getAspectRatioString = (ratio: number | null) => {
32
35
return 'free' ;
33
36
} ;
34
37
35
- export const EditorContainer = ( { editor } : Props ) => {
38
+ export const EditorContainer = ( { editor, onApplyCrop , onReady } : Props ) => {
36
39
const containerRef = useRef < HTMLDivElement > ( null ) ;
37
40
const [ zoom , setZoom ] = useState ( 100 ) ;
38
- const [ cropInProgress , setCropInProgress ] = useState ( false ) ;
39
41
const [ cropBox , setCropBox ] = useState < CropBox | null > ( null ) ;
40
- const [ cropApplied , setCropApplied ] = useState ( false ) ;
41
42
const [ aspectRatio , setAspectRatio ] = useState < string > ( 'free' ) ;
42
43
const autoAddBoardId = useAppSelector ( selectAutoAddBoardId ) ;
43
44
44
45
const [ uploadImage ] = useUploadImageMutation ( { fixedCacheKey : 'editorContainer' } ) ;
45
46
46
47
const setup = useCallback (
47
- ( container : HTMLDivElement ) => {
48
+ async ( container : HTMLDivElement ) => {
48
49
editor . init ( container ) ;
49
50
editor . onZoomChange ( ( zoom ) => {
50
51
setZoom ( zoom ) ;
51
52
} ) ;
52
- editor . onCropStart ( ( ) => {
53
- setCropInProgress ( true ) ;
54
- setCropBox ( null ) ;
55
- } ) ;
56
53
editor . onCropBoxChange ( ( crop ) => {
57
54
setCropBox ( crop ) ;
58
55
} ) ;
59
- editor . onCropApply ( ( ) => {
60
- setCropApplied ( true ) ;
61
- setCropInProgress ( false ) ;
62
- setCropBox ( null ) ;
63
- } ) ;
64
56
editor . onCropReset ( ( ) => {
65
- setCropApplied ( true ) ;
66
- setCropInProgress ( false ) ;
67
57
setCropBox ( null ) ;
68
58
} ) ;
69
- editor . onCropCancel ( ( ) => {
70
- setCropInProgress ( false ) ;
71
- setCropBox ( null ) ;
72
- } ) ;
73
- editor . onImageLoad ( ( ) => {
74
- // setCropInfo('');
75
- // setIsCropping(false);
76
- // setHasCropBbox(false);
77
- } ) ;
78
59
setAspectRatio ( getAspectRatioString ( editor . getCropAspectRatio ( ) ) ) ;
60
+ await onReady ( ) ;
79
61
editor . fitToContainer ( ) ;
80
62
} ,
81
- [ editor ]
63
+ [ editor , onReady ]
82
64
) ;
83
65
84
66
useEffect ( ( ) => {
@@ -98,14 +80,6 @@ export const EditorContainer = ({ editor }: Props) => {
98
80
} ;
99
81
} , [ editor , setup ] ) ;
100
82
101
- const handleStartCrop = useCallback ( ( ) => {
102
- editor . startCrop ( ) ;
103
- // Apply current aspect ratio if not free
104
- if ( aspectRatio !== 'free' ) {
105
- editor . setCropAspectRatio ( CROP_ASPECT_RATIO_MAP [ aspectRatio ] ?? null ) ;
106
- }
107
- } , [ aspectRatio , editor ] ) ;
108
-
109
83
const handleAspectRatioChange = useCallback (
110
84
( e : React . ChangeEvent < HTMLSelectElement > ) => {
111
85
const newRatio = e . target . value ;
@@ -120,18 +94,19 @@ export const EditorContainer = ({ editor }: Props) => {
120
94
[ editor ]
121
95
) ;
122
96
123
- const handleApplyCrop = useCallback ( ( ) => {
124
- editor . applyCrop ( ) ;
125
- } , [ editor ] ) ;
126
-
127
- const handleCancelCrop = useCallback ( ( ) => {
128
- editor . cancelCrop ( ) ;
129
- } , [ editor ] ) ;
130
-
131
97
const handleResetCrop = useCallback ( ( ) => {
132
98
editor . resetCrop ( ) ;
133
99
} , [ editor ] ) ;
134
100
101
+ const handleApplyCrop = useCallback ( async ( ) => {
102
+ await onApplyCrop ( ) ;
103
+ closeEditImageModal ( ) ;
104
+ } , [ onApplyCrop ] ) ;
105
+
106
+ const handleCancelCrop = useCallback ( ( ) => {
107
+ closeEditImageModal ( ) ;
108
+ } , [ ] ) ;
109
+
135
110
const handleExport = useCallback ( async ( ) => {
136
111
try {
137
112
const blob = await editor . exportImage ( 'blob' ) ;
@@ -144,7 +119,6 @@ export const EditorContainer = ({ editor }: Props) => {
144
119
board_id : autoAddBoardId === 'none' ? undefined : autoAddBoardId ,
145
120
} ) . unwrap ( ) ;
146
121
} catch ( err ) {
147
- console . error ( 'Export failed:' , err ) ;
148
122
if ( err instanceof Error && err . message . includes ( 'tainted' ) ) {
149
123
alert (
150
124
'Cannot export image: The image is from a different domain (CORS issue). To fix this:\n\n1. Load images from the same domain\n2. Use images from CORS-enabled sources\n3. Upload a local image file instead'
@@ -174,23 +148,19 @@ export const EditorContainer = ({ editor }: Props) => {
174
148
return (
175
149
< Flex w = "full" h = "full" flexDir = "column" gap = { 4 } >
176
150
< Flex gap = { 2 } >
177
- { ! cropInProgress && < Button onClick = { handleStartCrop } > Start Crop</ Button > }
178
- { cropApplied && < Button onClick = { handleResetCrop } > Reset Crop</ Button > }
179
- { cropInProgress && (
180
- < >
181
- < Select value = { aspectRatio } onChange = { handleAspectRatioChange } >
182
- < option value = "free" > Free</ option >
183
- < option value = "1:1" > 1:1 (Square)</ option >
184
- < option value = "4:3" > 4:3</ option >
185
- < option value = "16:9" > 16:9</ option >
186
- < option value = "3:2" > 3:2</ option >
187
- < option value = "2:3" > 2:3 (Portrait)</ option >
188
- < option value = "9:16" > 9:16 (Portrait)</ option >
189
- </ Select >
190
- < Button onClick = { handleApplyCrop } > Apply Crop</ Button >
191
- < Button onClick = { handleCancelCrop } > Cancel Crop</ Button >
192
- </ >
193
- ) }
151
+ { cropBox && < Button onClick = { handleResetCrop } > Reset Crop</ Button > }
152
+ < Select value = { aspectRatio } onChange = { handleAspectRatioChange } w = { 64 } >
153
+ < option value = "free" > Free</ option >
154
+ < option value = "16:9" > 16:9</ option >
155
+ < option value = "3:2" > 3:2</ option >
156
+ < option value = "4:3" > 4:3</ option >
157
+ < option value = "1:1" > 1:1 (Square)</ option >
158
+ < option value = "3:4" > 3:4</ option >
159
+ < option value = "2:3" > 2:3 (Portrait)</ option >
160
+ < option value = "9:16" > 9:16 (Portrait)</ option >
161
+ </ Select >
162
+ < Button onClick = { handleApplyCrop } > Apply Crop</ Button >
163
+ < Button onClick = { handleCancelCrop } > Cancel Crop</ Button >
194
164
195
165
< Button onClick = { fitToContainer } > Fit</ Button >
196
166
< Button onClick = { resetView } > Reset View</ Button >
@@ -208,13 +178,9 @@ export const EditorContainer = ({ editor }: Props) => {
208
178
< Text > Mouse wheel: Zoom</ Text >
209
179
< Divider orientation = "vertical" />
210
180
< Text > Space + Drag: Pan</ Text >
211
- { cropInProgress && (
212
- < >
213
- < Divider orientation = "vertical" />
214
- < Text > Drag crop box or handles to adjust</ Text >
215
- </ >
216
- ) }
217
- { cropInProgress && cropBox && (
181
+ < Divider orientation = "vertical" />
182
+ < Text > Drag crop box or handles to adjust</ Text >
183
+ { cropBox && (
218
184
< >
219
185
< Divider orientation = "vertical" />
220
186
< Text >
0 commit comments