Skip to content

Commit e1acbcd

Browse files
fix(ui): store floats for box
1 parent 7d9b815 commit e1acbcd

File tree

3 files changed

+80
-91
lines changed

3 files changed

+80
-91
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ export const zImageWithDims = z.object({
3838
export type ImageWithDims = z.infer<typeof zImageWithDims>;
3939

4040
const zCropBox = z.object({
41-
x: z.number().int().min(0),
42-
y: z.number().int().min(0),
43-
width: z.number().int().positive(),
44-
height: z.number().int().positive(),
41+
x: z.number().min(0),
42+
y: z.number().min(0),
43+
width: z.number().positive(),
44+
height: z.number().positive(),
4545
});
4646
export const zCroppableImageWithDims = z.object({
4747
original: zImageWithDims,

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

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
1-
import { Button, Divider, Flex, Select, Spacer, Text } from '@invoke-ai/ui-library';
1+
import {
2+
Button,
3+
ButtonGroup,
4+
Divider,
5+
Flex,
6+
FormControl,
7+
FormLabel,
8+
Select,
9+
Spacer,
10+
Text,
11+
} from '@invoke-ai/ui-library';
212
import { useAppSelector } from 'app/store/storeHooks';
313
import type { CropBox } from 'features/editImageModal/lib/editor';
414
import { closeEditImageModal, type EditImageModalState } from 'features/editImageModal/store';
@@ -53,9 +63,6 @@ export const EditorContainer = ({ editor, onApplyCrop, onReady }: Props) => {
5363
editor.onCropBoxChange((crop) => {
5464
setCropBox(crop);
5565
});
56-
editor.onCropReset(() => {
57-
setCropBox(null);
58-
});
5966
setAspectRatio(getAspectRatioString(editor.getCropAspectRatio()));
6067
await onReady();
6168
editor.fitToContainer();
@@ -147,27 +154,38 @@ export const EditorContainer = ({ editor, onApplyCrop, onReady }: Props) => {
147154

148155
return (
149156
<Flex w="full" h="full" flexDir="column" gap={4}>
150-
<Flex gap={2}>
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>
164-
165-
<Button onClick={fitToContainer}>Fit</Button>
166-
<Button onClick={resetView}>Reset View</Button>
167-
<Button onClick={zoomIn}>Zoom In</Button>
168-
<Button onClick={zoomOut}>Zoom Out</Button>
169-
170-
<Button onClick={handleExport}>Export</Button>
157+
<Flex gap={2} alignItems="center">
158+
<FormControl flex={1}>
159+
<FormLabel>Aspect Ratio:</FormLabel>
160+
<Select size="sm" value={aspectRatio} onChange={handleAspectRatioChange} w={64}>
161+
<option value="free">Free</option>
162+
<option value="16:9">16:9</option>
163+
<option value="3:2">3:2</option>
164+
<option value="4:3">4:3</option>
165+
<option value="1:1">1:1 (Square)</option>
166+
<option value="3:4">3:4</option>
167+
<option value="2:3">2:3 (Portrait)</option>
168+
<option value="9:16">9:16 (Portrait)</option>
169+
</Select>
170+
</FormControl>
171+
172+
<Spacer />
173+
174+
<ButtonGroup size="sm" isAttached={false}>
175+
<Button onClick={fitToContainer}>Fit View</Button>
176+
<Button onClick={resetView}>Reset View</Button>
177+
<Button onClick={zoomIn}>Zoom In</Button>
178+
<Button onClick={zoomOut}>Zoom Out</Button>
179+
</ButtonGroup>
180+
181+
<Spacer />
182+
183+
<ButtonGroup size="sm" isAttached={false}>
184+
<Button onClick={handleApplyCrop}>Apply</Button>
185+
<Button onClick={handleResetCrop}>Reset</Button>
186+
<Button onClick={handleCancelCrop}>Cancel</Button>
187+
<Button onClick={handleExport}>Export</Button>
188+
</ButtonGroup>
171189
</Flex>
172190

173191
<Flex position="relative" w="full" h="full" bg="base.900">

invokeai/frontend/web/src/features/editImageModal/lib/editor.ts

Lines changed: 33 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@ export type CropBox = {
1919
*/
2020
type EditorCallbacks = {
2121
onCropBoxChange: Set<(crop: Readonly<CropBox>) => void>;
22-
onCropReset: Set<() => void>;
2322
onZoomChange: Set<(zoom: number) => void>;
24-
onImageLoad: Set<() => void>;
2523
};
2624

27-
type SetElement<T> = T extends Set<infer U> ? U : never;
28-
2925
/**
3026
* Crop box resize handle names.
3127
*/
@@ -183,9 +179,7 @@ export class Editor {
183179

184180
private callbacks: EditorCallbacks = {
185181
onCropBoxChange: new Set(),
186-
onCropReset: new Set(),
187182
onZoomChange: new Set(),
188-
onImageLoad: new Set(),
189183
};
190184

191185
private cropBox: CropBox | null = null;
@@ -603,7 +597,9 @@ export class Editor {
603597
this.updateKonvaCropInteractionRect();
604598
this.updateKonvaCropInteractionGuides();
605599
this.updateKonvaCropInteractionHandlePositions();
606-
this._invokeCallbacks('onCropBoxChange', cropBox);
600+
for (const cb of this.callbacks.onCropBoxChange) {
601+
cb(cropBox);
602+
}
607603
};
608604

609605
/**
@@ -1007,7 +1003,9 @@ export class Editor {
10071003
// Update handle scaling to maintain constant screen size
10081004
this.updateKonvaCropInteractionHandleScales();
10091005
this.updateKonvaBg();
1010-
this._invokeCallbacks('onZoomChange', newScale);
1006+
for (const cb of this.callbacks.onZoomChange) {
1007+
cb(newScale);
1008+
}
10111009
};
10121010

10131011
/**
@@ -1103,7 +1101,6 @@ export class Editor {
11031101
img.onload = () => {
11041102
this.originalImage = img;
11051103
this.updateImage(initial);
1106-
this._invokeCallbacks('onImageLoad');
11071104
resolve();
11081105
};
11091106

@@ -1119,14 +1116,14 @@ export class Editor {
11191116
* Reset the crop box to encompass the entire image.
11201117
*/
11211118
resetCrop = () => {
1122-
if (this.konva?.image.image) {
1123-
this.updateCropBox({
1124-
x: 0,
1125-
y: 0,
1126-
...this.konva.image.image.size(),
1127-
});
1119+
if (!this.konva?.image.image) {
1120+
return;
11281121
}
1129-
this._invokeCallbacks('onCropReset');
1122+
this.updateCropBox({
1123+
x: 0,
1124+
y: 0,
1125+
...this.konva.image.image.size(),
1126+
});
11301127
};
11311128

11321129
/**
@@ -1276,7 +1273,9 @@ export class Editor {
12761273

12771274
this.updateKonvaBg();
12781275

1279-
this._invokeCallbacks('onZoomChange', scale);
1276+
for (const cb of this.callbacks.onZoomChange) {
1277+
cb(scale);
1278+
}
12801279
};
12811280

12821281
/**
@@ -1330,7 +1329,9 @@ export class Editor {
13301329

13311330
this.updateKonvaBg();
13321331

1333-
this._invokeCallbacks('onZoomChange', 1);
1332+
for (const cb of this.callbacks.onZoomChange) {
1333+
cb(1);
1334+
}
13341335
};
13351336

13361337
/**
@@ -1366,7 +1367,9 @@ export class Editor {
13661367

13671368
this.updateKonvaBg();
13681369

1369-
this._invokeCallbacks('onZoomChange', scale);
1370+
for (const cb of this.callbacks.onZoomChange) {
1371+
cb(scale);
1372+
}
13701373
};
13711374

13721375
/**
@@ -1476,56 +1479,24 @@ export class Editor {
14761479
};
14771480

14781481
/**
1479-
* Helper to build a callback registrar function for a specific event name.
1480-
* @param name The callback event name.
1482+
* Register a callback for when the crop box changes (moved or resized).
14811483
*/
1482-
_buildCallbackRegistrar = <T extends keyof EditorCallbacks>(name: T) => {
1483-
return (cb: SetElement<EditorCallbacks[T]>): (() => void) => {
1484-
(this.callbacks[name] as Set<typeof cb>).add(cb);
1485-
return () => {
1486-
(this.callbacks[name] as Set<typeof cb>).delete(cb);
1487-
};
1484+
onCropBoxChange = (cb: (crop: Readonly<CropBox>) => void): (() => void) => {
1485+
this.callbacks.onCropBoxChange.add(cb);
1486+
return () => {
1487+
this.callbacks.onCropBoxChange.delete(cb);
14881488
};
14891489
};
14901490

1491-
/**
1492-
* Invoke all callbacks registered for a specific event.
1493-
* @param name The callback event name.
1494-
* @param args The arguments to pass to each callback.
1495-
*/
1496-
private _invokeCallbacks = <T extends keyof EditorCallbacks>(
1497-
name: T,
1498-
...args: EditorCallbacks[T] extends Set<(...args: infer P) => void> ? P : never
1499-
): void => {
1500-
const callbacks = this.callbacks[name];
1501-
if (callbacks && callbacks.size > 0) {
1502-
callbacks.forEach((cb) => {
1503-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1504-
(cb as (...args: any[]) => void)(...args);
1505-
});
1506-
}
1507-
};
1508-
1509-
/**
1510-
* Register a callback for when the crop is reset.
1511-
*/
1512-
onCropReset = this._buildCallbackRegistrar('onCropReset');
1513-
1514-
/**
1515-
* Register a callback for when the crop box changes (moved or resized).
1516-
*/
1517-
onCropBoxChange = this._buildCallbackRegistrar('onCropBoxChange');
1518-
1519-
/**
1520-
* Register a callback for when a new image is loaded.
1521-
*/
1522-
onImageLoad = this._buildCallbackRegistrar('onImageLoad');
1523-
15241491
/**
15251492
* Register a callback for when the zoom level changes.
15261493
*/
1527-
onZoomChange = this._buildCallbackRegistrar('onZoomChange');
1528-
1494+
onZoomChange = (cb: (zoom: number) => void): (() => void) => {
1495+
this.callbacks.onZoomChange.add(cb);
1496+
return () => {
1497+
this.callbacks.onZoomChange.delete(cb);
1498+
};
1499+
};
15291500
/**
15301501
* Resize the editor container and adjust the Konva stage accordingly.
15311502
*

0 commit comments

Comments
 (0)