Skip to content

Commit b51a232

Browse files
feat(ui): extract config to own obj
1 parent 4412143 commit b51a232

File tree

1 file changed

+77
-49
lines changed
  • invokeai/frontend/web/src/features/editImageModal/lib

1 file changed

+77
-49
lines changed

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

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,59 @@ type OutputFormatToOutputMap<T extends OutputFormat> = T extends 'canvas'
6969
? string
7070
: never;
7171

72+
type EditorConfig = {
73+
MIN_CROP_DIMENSION: number;
74+
75+
ZOOM_WHEEL_FACTOR: number;
76+
ZOOM_BUTTON_FACTOR: number;
77+
78+
CROP_HANDLE_SIZE: number;
79+
CROP_HANDLE_STROKE_WIDTH: number;
80+
CROP_HANDLE_FILL: string;
81+
CROP_HANDLE_STROKE: string;
82+
83+
CROP_GUIDE_STROKE: string;
84+
CROP_GUIDE_STROKE_WIDTH: number;
85+
86+
FIT_TO_CONTAINER_PADDING: number;
87+
88+
DEFAULT_CROP_BOX_SCALE: number;
89+
90+
ZOOM_MIN: number;
91+
ZOOM_MAX: number;
92+
};
93+
94+
const DEFAULT_CONFIG: EditorConfig = {
95+
MIN_CROP_DIMENSION: 64,
96+
97+
ZOOM_WHEEL_FACTOR: 1.1,
98+
ZOOM_BUTTON_FACTOR: 1.2,
99+
100+
CROP_HANDLE_SIZE: 8,
101+
CROP_HANDLE_STROKE_WIDTH: 1,
102+
CROP_HANDLE_FILL: 'white',
103+
CROP_HANDLE_STROKE: 'black',
104+
105+
CROP_GUIDE_STROKE: 'rgba(255, 255, 255, 0.5)',
106+
CROP_GUIDE_STROKE_WIDTH: 1,
107+
108+
FIT_TO_CONTAINER_PADDING: 0.9,
109+
110+
DEFAULT_CROP_BOX_SCALE: 0.8,
111+
112+
ZOOM_MIN: 0.1,
113+
ZOOM_MAX: 10,
114+
};
115+
72116
export class Editor {
73117
private konva: KonvaObjects | null = null;
74118
private originalImage: HTMLImageElement | null = null;
75119
private isCropping = false;
76-
77-
// Constants
78-
private readonly MIN_CROP_DIMENSION = 64;
79-
private readonly ZOOM_WHEEL_FACTOR = 1.1;
80-
private readonly ZOOM_BUTTON_FACTOR = 1.2;
81-
private readonly CROP_HANDLE_SIZE = 8;
82-
private readonly CROP_HANDLE_STROKE_WIDTH = 1;
83-
private readonly CROP_GUIDE_STROKE = 'rgba(255, 255, 255, 0.5)';
84-
private readonly CROP_GUIDE_STROKE_WIDTH = 1;
85-
private readonly CROP_HANDLE_FILL = 'white';
86-
private readonly CROP_HANDLE_STROKE = 'black';
87-
private readonly FIT_TO_CONTAINER_PADDING = 0.9;
88-
private readonly DEFAULT_CROP_BOX_SCALE = 0.8;
89-
90-
// Configuration
91-
private readonly ZOOM_MIN = 0.1;
92-
private readonly ZOOM_MAX = 10;
120+
private config: EditorConfig = DEFAULT_CONFIG;
93121

94122
private cropConstraints: CropConstraints = {
95-
minWidth: this.MIN_CROP_DIMENSION,
96-
minHeight: this.MIN_CROP_DIMENSION,
123+
minWidth: this.config.MIN_CROP_DIMENSION,
124+
minHeight: this.config.MIN_CROP_DIMENSION,
97125
};
98126
private callbacks: EditorCallbacks = {};
99127
private cropBox: CropBox | null = null;
@@ -105,7 +133,9 @@ export class Editor {
105133

106134
private subscriptions: Set<() => void> = new Set();
107135

108-
init = (container: HTMLDivElement) => {
136+
init = (container: HTMLDivElement, config?: Partial<EditorConfig>) => {
137+
this.config = { ...this.config, ...config };
138+
109139
const stage = new Konva.Stage({
110140
container: container,
111141
width: container.clientWidth,
@@ -245,24 +275,21 @@ export class Editor {
245275
if (!this.konva?.image.image || !this.cropBox) {
246276
return;
247277
}
278+
248279
const imgWidth = this.konva.image.image.width();
249280
const imgHeight = this.konva.image.image.height();
250281

251282
// Constrain to image bounds
252283
const x = Math.max(0, Math.min(rect.x(), imgWidth - rect.width()));
253284
const y = Math.max(0, Math.min(rect.y(), imgHeight - rect.height()));
285+
const { width, height } = this.cropBox;
254286

255287
rect.x(x);
256288
rect.y(y);
257289

258-
this.updateCropBox({
259-
...this.cropBox,
260-
x,
261-
y,
262-
});
290+
this.updateCropBox({ x, y, width, height });
263291
});
264292

265-
// Cursor styles
266293
rect.on('mouseenter', () => {
267294
const stage = this.konva?.stage;
268295
if (!stage) {
@@ -289,8 +316,8 @@ export class Editor {
289316
private createKonvaCropGuide = (name: GuideName): Konva.Line => {
290317
const line = new Konva.Line({
291318
name,
292-
stroke: this.CROP_GUIDE_STROKE,
293-
strokeWidth: this.CROP_GUIDE_STROKE_WIDTH,
319+
stroke: this.config.CROP_GUIDE_STROKE,
320+
strokeWidth: this.config.CROP_GUIDE_STROKE_WIDTH,
294321
strokeScaleEnabled: false,
295322
listening: false,
296323
});
@@ -303,11 +330,11 @@ export class Editor {
303330
name,
304331
x: 0,
305332
y: 0,
306-
width: this.CROP_HANDLE_SIZE,
307-
height: this.CROP_HANDLE_SIZE,
308-
fill: this.CROP_HANDLE_FILL,
309-
stroke: this.CROP_HANDLE_STROKE,
310-
strokeWidth: this.CROP_HANDLE_STROKE_WIDTH,
333+
width: this.config.CROP_HANDLE_SIZE,
334+
height: this.config.CROP_HANDLE_SIZE,
335+
fill: this.config.CROP_HANDLE_FILL,
336+
stroke: this.config.CROP_HANDLE_STROKE,
337+
strokeWidth: this.config.CROP_HANDLE_STROKE_WIDTH,
311338
strokeScaleEnabled: true,
312339
draggable: true,
313340
hitStrokeWidth: 16,
@@ -466,8 +493,8 @@ export class Editor {
466493
}
467494

468495
const scale = this.konva.stage.scaleX();
469-
const handleSize = this.CROP_HANDLE_SIZE / scale;
470-
const strokeWidth = this.CROP_HANDLE_STROKE_WIDTH / scale;
496+
const handleSize = this.config.CROP_HANDLE_SIZE / scale;
497+
const strokeWidth = this.config.CROP_HANDLE_STROKE_WIDTH / scale;
471498

472499
for (const handle of Object.values(this.konva.crop.interaction.handles)) {
473500
const currentX = handle.x();
@@ -561,11 +588,11 @@ export class Editor {
561588
};
562589

563590
const direction = e.deltaY > 0 ? -1 : 1;
564-
const scaleBy = this.ZOOM_WHEEL_FACTOR;
591+
const scaleBy = this.config.ZOOM_WHEEL_FACTOR;
565592
let newScale = direction > 0 ? oldScale * scaleBy : oldScale / scaleBy;
566593

567594
// Apply zoom limits
568-
newScale = Math.max(this.ZOOM_MIN, Math.min(this.ZOOM_MAX, newScale));
595+
newScale = Math.max(this.config.ZOOM_MIN, Math.min(this.config.ZOOM_MAX, newScale));
569596

570597
this.konva.stage.scale({ x: newScale, y: newScale });
571598

@@ -727,8 +754,8 @@ export class Editor {
727754
const handleX = handleRect.x() + handleRect.width() / 2;
728755
const handleY = handleRect.y() + handleRect.height() / 2;
729756

730-
const minWidth = this.cropConstraints.minWidth ?? this.MIN_CROP_DIMENSION;
731-
const minHeight = this.cropConstraints.minHeight ?? this.MIN_CROP_DIMENSION;
757+
const minWidth = this.cropConstraints.minWidth ?? this.config.MIN_CROP_DIMENSION;
758+
const minHeight = this.cropConstraints.minHeight ?? this.config.MIN_CROP_DIMENSION;
732759

733760
// Update dimensions based on handle type
734761
if (handleName.includes('left')) {
@@ -762,8 +789,8 @@ export class Editor {
762789
const handleX = handleRect.x() + handleRect.width() / 2;
763790
const handleY = handleRect.y() + handleRect.height() / 2;
764791

765-
const minWidth = this.cropConstraints.minWidth ?? this.MIN_CROP_DIMENSION;
766-
const minHeight = this.cropConstraints.minHeight ?? this.MIN_CROP_DIMENSION;
792+
const minWidth = this.cropConstraints.minWidth ?? this.config.MIN_CROP_DIMENSION;
793+
const minHeight = this.cropConstraints.minHeight ?? this.config.MIN_CROP_DIMENSION;
767794

768795
// Early boundary check for aspect ratio mode
769796
const atLeftEdge = this.cropBox.x <= 0;
@@ -951,8 +978,8 @@ export class Editor {
951978
// Create default crop box (centered, 80% of image)
952979
const imgWidth = this.konva.image.image.width();
953980
const imgHeight = this.konva.image.image.height();
954-
cropWidth = imgWidth * this.DEFAULT_CROP_BOX_SCALE;
955-
cropHeight = imgHeight * this.DEFAULT_CROP_BOX_SCALE;
981+
cropWidth = imgWidth * this.config.DEFAULT_CROP_BOX_SCALE;
982+
cropHeight = imgHeight * this.config.DEFAULT_CROP_BOX_SCALE;
956983
cropX = (imgWidth - cropWidth) / 2;
957984
cropY = (imgHeight - cropHeight) / 2;
958985
}
@@ -1072,7 +1099,7 @@ export class Editor {
10721099
return;
10731100
}
10741101

1075-
scale = Math.max(this.ZOOM_MIN, Math.min(this.ZOOM_MAX, scale));
1102+
scale = Math.max(this.config.ZOOM_MIN, Math.min(this.config.ZOOM_MAX, scale));
10761103

10771104
// If no point provided, use center of viewport
10781105
if (!point && this.konva.image) {
@@ -1116,12 +1143,12 @@ export class Editor {
11161143

11171144
zoomIn = (point?: { x: number; y: number }) => {
11181145
const currentZoom = this.getZoom();
1119-
this.setZoom(currentZoom * this.ZOOM_BUTTON_FACTOR, point);
1146+
this.setZoom(currentZoom * this.config.ZOOM_BUTTON_FACTOR, point);
11201147
};
11211148

11221149
zoomOut = (point?: { x: number; y: number }) => {
11231150
const currentZoom = this.getZoom();
1124-
this.setZoom(currentZoom / this.ZOOM_BUTTON_FACTOR, point);
1151+
this.setZoom(currentZoom / this.config.ZOOM_BUTTON_FACTOR, point);
11251152
};
11261153

11271154
resetView = () => {
@@ -1160,7 +1187,8 @@ export class Editor {
11601187
const imageWidth = this.konva.image.image.width();
11611188
const imageHeight = this.konva.image.image.height();
11621189

1163-
const scale = Math.min(containerWidth / imageWidth, containerHeight / imageHeight) * this.FIT_TO_CONTAINER_PADDING;
1190+
const scale =
1191+
Math.min(containerWidth / imageWidth, containerHeight / imageHeight) * this.config.FIT_TO_CONTAINER_PADDING;
11641192

11651193
this.konva.stage.scale({ x: scale, y: scale });
11661194

@@ -1230,8 +1258,8 @@ export class Editor {
12301258
}
12311259

12321260
// Apply minimum size constraints
1233-
const minWidth = this.cropConstraints.minWidth ?? this.MIN_CROP_DIMENSION;
1234-
const minHeight = this.cropConstraints.minHeight ?? this.MIN_CROP_DIMENSION;
1261+
const minWidth = this.cropConstraints.minWidth ?? this.config.MIN_CROP_DIMENSION;
1262+
const minHeight = this.cropConstraints.minHeight ?? this.config.MIN_CROP_DIMENSION;
12351263

12361264
if (newWidth < minWidth) {
12371265
newWidth = minWidth;

0 commit comments

Comments
 (0)