Skip to content

Commit 6a447dd

Browse files
refactor(ui): remove "apply", "start" and "cancel" concepts from editor
1 parent c2dc63d commit 6a447dd

File tree

13 files changed

+112
-283
lines changed

13 files changed

+112
-283
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"copy": "Copy",
105105
"copyError": "$t(gallery.copy) Error",
106106
"clipboard": "Clipboard",
107+
"crop": "Crop",
107108
"on": "On",
108109
"off": "Off",
109110
"or": "or",

invokeai/frontend/web/src/features/controlLayers/store/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const zCropBox = z.object({
4343
width: z.number().int().positive(),
4444
height: z.number().int().positive(),
4545
});
46-
export const zCroppableImage = z.object({
46+
export const zCroppableImageWithDims = z.object({
4747
original: zImageWithDims,
4848
crop: z
4949
.object({
@@ -53,7 +53,7 @@ export const zCroppableImage = z.object({
5353
})
5454
.optional(),
5555
});
56-
export type CroppableImageWithDims = z.infer<typeof zCroppableImage>;
56+
export type CroppableImageWithDims = z.infer<typeof zCroppableImageWithDims>;
5757

5858
const zImageWithDimsDataURL = z.object({
5959
dataURL: z.string(),

invokeai/frontend/web/src/features/dnd/DndImage.tsx

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@ import { singleImageDndSource } from 'features/dnd/dnd';
99
import type { DndDragPreviewSingleImageState } from 'features/dnd/DndDragPreviewSingleImage';
1010
import { createSingleImageDragPreview, setSingleImageDragPreview } from 'features/dnd/DndDragPreviewSingleImage';
1111
import { firefoxDndFix } from 'features/dnd/util';
12-
import { Editor } from 'features/editImageModal/lib/editor';
13-
import { openEditImageModal } from 'features/editImageModal/store';
1412
import { useImageContextMenu } from 'features/gallery/components/ContextMenu/ImageContextMenu';
15-
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
13+
import { forwardRef, memo, useEffect, useImperativeHandle, useRef, useState } from 'react';
1614
import type { ImageDTO } from 'services/api/types';
1715

1816
const sx = {
@@ -28,14 +26,12 @@ const sx = {
2826
type Props = {
2927
imageDTO: ImageDTO;
3028
asThumbnail?: boolean;
31-
editable?: boolean;
3229
} & ImageProps;
3330

3431
export const DndImage = memo(
35-
forwardRef(({ imageDTO, asThumbnail, editable, ...rest }: Props, forwardedRef) => {
32+
forwardRef(({ imageDTO, asThumbnail, ...rest }: Props, forwardedRef) => {
3633
const store = useAppStore();
3734
const crossOrigin = useStore($crossOrigin);
38-
const [previewDataURL, setPreviewDataURl] = useState<string | null>(null);
3935

4036
const [isDragging, setIsDragging] = useState(false);
4137
const ref = useRef<HTMLImageElement>(null);
@@ -73,32 +69,18 @@ export const DndImage = memo(
7369

7470
useImageContextMenu(imageDTO, ref);
7571

76-
const edit = useCallback(() => {
77-
if (!editable) {
78-
return;
79-
}
80-
81-
const editor = new Editor();
82-
editor.onCropApply(async () => {
83-
const previewDataURL = await editor.exportImage('dataURL', { withCropOverlay: true });
84-
setPreviewDataURl(previewDataURL);
85-
});
86-
openEditImageModal(imageDTO.image_name, editor);
87-
}, [editable, imageDTO.image_name]);
88-
8972
return (
9073
<>
9174
<Image
9275
role="button"
9376
ref={ref}
94-
src={previewDataURL ?? (asThumbnail ? imageDTO.thumbnail_url : imageDTO.image_url)}
77+
src={asThumbnail ? imageDTO.thumbnail_url : imageDTO.image_url}
9578
fallbackSrc={asThumbnail ? undefined : imageDTO.thumbnail_url}
9679
width={imageDTO.width}
9780
height={imageDTO.height}
9881
sx={sx}
9982
data-is-dragging={isDragging}
10083
crossOrigin={!asThumbnail ? crossOrigin : undefined}
101-
onClick={edit}
10284
{...rest}
10385
/>
10486
{dragPreviewState?.type === 'single-image' ? createSingleImageDragPreview(dragPreviewState) : null}

invokeai/frontend/web/src/features/editImageModal/components/EditImageModal.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import { EditorContainer } from './EditorContainer';
77
export const EditImageModal = () => {
88
const state = useStore($editImageModalState);
99

10+
if (!state) {
11+
return null;
12+
}
13+
1014
return (
11-
<Modal isOpen={state.isOpen} onClose={closeEditImageModal} isCentered useInert={false} size="full">
15+
<Modal isOpen={true} onClose={closeEditImageModal} isCentered useInert={false} size="full">
1216
<ModalOverlay />
1317
<ModalContent minH="unset" minW="unset" maxH="90vh" maxW="90vw" w="full" h="full" borderRadius="base">
14-
<ModalHeader>Edit Image</ModalHeader>
18+
<ModalHeader>Crop Image</ModalHeader>
1519
<ModalBody px={4} pb={4} pt={0}>
16-
{state.isOpen && <EditorContainer editor={state.editor} imageName={state.imageName} />}
20+
<EditorContainer editor={state.editor} onApplyCrop={state.onApplyCrop} onReady={state.onReady} />
1721
</ModalBody>
1822
</ModalContent>
1923
</Modal>

invokeai/frontend/web/src/features/editImageModal/components/EditorContainer.tsx

Lines changed: 35 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { Button, Divider, Flex, Select, Spacer, Text } from '@invoke-ai/ui-library';
22
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';
45
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
56
import React, { useCallback, useEffect, useRef, useState } from 'react';
67
import { useUploadImageMutation } from 'services/api/endpoints/images';
78

89
type Props = {
9-
editor: Editor;
10+
editor: EditImageModalState['editor'];
11+
onApplyCrop: EditImageModalState['onApplyCrop'];
12+
onReady: EditImageModalState['onReady'];
1013
};
1114

1215
const 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>

invokeai/frontend/web/src/features/editImageModal/hooks/useEditor.ts

Lines changed: 0 additions & 33 deletions
This file was deleted.

0 commit comments

Comments
 (0)