11import { Button , Divider , Flex , Select , Spacer , Text } from '@invoke-ai/ui-library' ;
22import { 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' ;
45import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors' ;
56import React , { useCallback , useEffect , useRef , useState } from 'react' ;
67import { useUploadImageMutation } from 'services/api/endpoints/images' ;
78
89type Props = {
9- editor : Editor ;
10+ editor : EditImageModalState [ 'editor' ] ;
11+ onApplyCrop : EditImageModalState [ 'onApplyCrop' ] ;
12+ onReady : EditImageModalState [ 'onReady' ] ;
1013} ;
1114
1215const CROP_ASPECT_RATIO_MAP : Record < string , number > = {
@@ -19,7 +22,7 @@ const CROP_ASPECT_RATIO_MAP: Record<string, number> = {
1922 '9:16' : 9 / 16 ,
2023} ;
2124
22- export const getAspectRatioString = ( ratio : number | null ) => {
25+ const getAspectRatioString = ( ratio : number | null ) => {
2326 if ( ! ratio ) {
2427 return 'free' ;
2528 }
@@ -32,53 +35,32 @@ export const getAspectRatioString = (ratio: number | null) => {
3235 return 'free' ;
3336} ;
3437
35- export const EditorContainer = ( { editor } : Props ) => {
38+ export const EditorContainer = ( { editor, onApplyCrop , onReady } : Props ) => {
3639 const containerRef = useRef < HTMLDivElement > ( null ) ;
3740 const [ zoom , setZoom ] = useState ( 100 ) ;
38- const [ cropInProgress , setCropInProgress ] = useState ( false ) ;
3941 const [ cropBox , setCropBox ] = useState < CropBox | null > ( null ) ;
40- const [ cropApplied , setCropApplied ] = useState ( false ) ;
4142 const [ aspectRatio , setAspectRatio ] = useState < string > ( 'free' ) ;
4243 const autoAddBoardId = useAppSelector ( selectAutoAddBoardId ) ;
4344
4445 const [ uploadImage ] = useUploadImageMutation ( { fixedCacheKey : 'editorContainer' } ) ;
4546
4647 const setup = useCallback (
47- ( container : HTMLDivElement ) => {
48+ async ( container : HTMLDivElement ) => {
4849 editor . init ( container ) ;
4950 editor . onZoomChange ( ( zoom ) => {
5051 setZoom ( zoom ) ;
5152 } ) ;
52- editor . onCropStart ( ( ) => {
53- setCropInProgress ( true ) ;
54- setCropBox ( null ) ;
55- } ) ;
5653 editor . onCropBoxChange ( ( crop ) => {
5754 setCropBox ( crop ) ;
5855 } ) ;
59- editor . onCropApply ( ( ) => {
60- setCropApplied ( true ) ;
61- setCropInProgress ( false ) ;
62- setCropBox ( null ) ;
63- } ) ;
6456 editor . onCropReset ( ( ) => {
65- setCropApplied ( true ) ;
66- setCropInProgress ( false ) ;
6757 setCropBox ( null ) ;
6858 } ) ;
69- editor . onCropCancel ( ( ) => {
70- setCropInProgress ( false ) ;
71- setCropBox ( null ) ;
72- } ) ;
73- editor . onImageLoad ( ( ) => {
74- // setCropInfo('');
75- // setIsCropping(false);
76- // setHasCropBbox(false);
77- } ) ;
7859 setAspectRatio ( getAspectRatioString ( editor . getCropAspectRatio ( ) ) ) ;
60+ await onReady ( ) ;
7961 editor . fitToContainer ( ) ;
8062 } ,
81- [ editor ]
63+ [ editor , onReady ]
8264 ) ;
8365
8466 useEffect ( ( ) => {
@@ -98,14 +80,6 @@ export const EditorContainer = ({ editor }: Props) => {
9880 } ;
9981 } , [ editor , setup ] ) ;
10082
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-
10983 const handleAspectRatioChange = useCallback (
11084 ( e : React . ChangeEvent < HTMLSelectElement > ) => {
11185 const newRatio = e . target . value ;
@@ -120,18 +94,19 @@ export const EditorContainer = ({ editor }: Props) => {
12094 [ editor ]
12195 ) ;
12296
123- const handleApplyCrop = useCallback ( ( ) => {
124- editor . applyCrop ( ) ;
125- } , [ editor ] ) ;
126-
127- const handleCancelCrop = useCallback ( ( ) => {
128- editor . cancelCrop ( ) ;
129- } , [ editor ] ) ;
130-
13197 const handleResetCrop = useCallback ( ( ) => {
13298 editor . resetCrop ( ) ;
13399 } , [ editor ] ) ;
134100
101+ const handleApplyCrop = useCallback ( async ( ) => {
102+ await onApplyCrop ( ) ;
103+ closeEditImageModal ( ) ;
104+ } , [ onApplyCrop ] ) ;
105+
106+ const handleCancelCrop = useCallback ( ( ) => {
107+ closeEditImageModal ( ) ;
108+ } , [ ] ) ;
109+
135110 const handleExport = useCallback ( async ( ) => {
136111 try {
137112 const blob = await editor . exportImage ( 'blob' ) ;
@@ -144,7 +119,6 @@ export const EditorContainer = ({ editor }: Props) => {
144119 board_id : autoAddBoardId === 'none' ? undefined : autoAddBoardId ,
145120 } ) . unwrap ( ) ;
146121 } catch ( err ) {
147- console . error ( 'Export failed:' , err ) ;
148122 if ( err instanceof Error && err . message . includes ( 'tainted' ) ) {
149123 alert (
150124 '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) => {
174148 return (
175149 < Flex w = "full" h = "full" flexDir = "column" gap = { 4 } >
176150 < 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 >
194164
195165 < Button onClick = { fitToContainer } > Fit</ Button >
196166 < Button onClick = { resetView } > Reset View</ Button >
@@ -208,13 +178,9 @@ export const EditorContainer = ({ editor }: Props) => {
208178 < Text > Mouse wheel: Zoom</ Text >
209179 < Divider orientation = "vertical" />
210180 < 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 && (
218184 < >
219185 < Divider orientation = "vertical" />
220186 < Text >
0 commit comments