Skip to content

Commit 8e2734d

Browse files
committed
Merge branch 'file-upload-image-crop' into avatar-file-processor
2 parents 662416c + 983dc4d commit 8e2734d

File tree

2 files changed

+86
-44
lines changed
  • ts/WoltLabSuite/Core/Component/Image
  • wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image

2 files changed

+86
-44
lines changed

ts/WoltLabSuite/Core/Component/Image/Cropper.ts

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ export interface CropperConfiguration {
2525
}[];
2626
}
2727

28+
function inSelection(selection: Selection, maxSelection: Selection): boolean {
29+
return (
30+
selection.x >= maxSelection.x &&
31+
selection.y >= maxSelection.y &&
32+
selection.x + selection.width <= maxSelection.x + maxSelection.width &&
33+
selection.y + selection.height <= maxSelection.y + maxSelection.height
34+
);
35+
}
36+
2837
abstract class ImageCropper {
2938
readonly configuration: CropperConfiguration;
3039
readonly file: File;
@@ -128,7 +137,7 @@ abstract class ImageCropper {
128137
protected setCropperStyle() {
129138
this.cropperCanvas!.style.aspectRatio = `${this.width}/${this.height}`;
130139

131-
if (this.width > this.height) {
140+
if (this.width >= this.height) {
132141
this.cropperCanvas!.style.width = `min(70vw, ${this.width}px)`;
133142
this.cropperCanvas!.style.height = "auto";
134143
} else {
@@ -153,33 +162,34 @@ abstract class ImageCropper {
153162
if (this.orientation) {
154163
this.cropperImage!.$rotate(`${this.orientation}deg`);
155164
}
156-
this.cropperImage!.$center("contain");
157-
this.cropperSelection!.$center();
158-
this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" });
165+
166+
this.centerSelection();
159167

160168
// Limit the selection to the canvas boundaries
161169
this.cropperSelection!.addEventListener("change", (event: CustomEvent) => {
162170
// see https://fengyuanchen.github.io/cropperjs/v2/api/cropper-selection.html#limit-boundaries
163171
const cropperCanvasRect = this.cropperCanvas!.getBoundingClientRect();
164172
const selection = event.detail as Selection;
165173

174+
const cropperImageRect = this.cropperImage!.getBoundingClientRect();
166175
const maxSelection: Selection = {
167-
x: 0,
168-
y: 0,
169-
width: cropperCanvasRect.width,
170-
height: cropperCanvasRect.height,
176+
x: Math.round(cropperImageRect.left - cropperCanvasRect.left),
177+
y: Math.round(cropperImageRect.top - cropperCanvasRect.top),
178+
width: Math.round(cropperImageRect.width),
179+
height: Math.round(cropperImageRect.height),
171180
};
172181

173-
if (
174-
selection.x < maxSelection.x ||
175-
selection.y < maxSelection.y ||
176-
selection.x + selection.width > maxSelection.x + maxSelection.width ||
177-
selection.y + selection.height > maxSelection.y + maxSelection.height
178-
) {
182+
if (!inSelection(selection, maxSelection)) {
179183
event.preventDefault();
180184
}
181185
});
182186
}
187+
188+
protected centerSelection(): void {
189+
this.cropperImage!.$center("contain");
190+
this.cropperSelection!.$center();
191+
this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" });
192+
}
183193
}
184194

185195
class ExactImageCropper extends ImageCropper {
@@ -284,11 +294,11 @@ class MinMaxImageCropper extends ImageCropper {
284294

285295
protected getCropperTemplate(): string {
286296
return `<div class="cropperContainer">
287-
<cropper-canvas background>
297+
<cropper-canvas background scale-step="0.0">
288298
<cropper-image skewable scalable translatable rotatable></cropper-image>
289299
<cropper-shade hidden></cropper-shade>
290-
<cropper-handle action="move" plain></cropper-handle>
291-
<cropper-selection movable zoomable resizable outlined>
300+
<cropper-handle action="scale" hidden disabled></cropper-handle>
301+
<cropper-selection movable resizable outlined>
292302
<cropper-grid role="grid" bordered covered></cropper-grid>
293303
<cropper-crosshair centered></cropper-crosshair>
294304
<cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
@@ -308,18 +318,18 @@ class MinMaxImageCropper extends ImageCropper {
308318
protected setCropperStyle() {
309319
super.setCropperStyle();
310320

311-
this.cropperSelection!.width = this.minSize.width;
312-
this.cropperSelection!.height = this.minSize.height;
313-
this.cropperCanvas!.style.minWidth = `min(${this.maxSize.width}px, ${this.width}px)`;
314-
this.cropperCanvas!.style.minHeight = `min(${this.maxSize.height}px, ${this.height}px)`;
321+
if (this.width >= this.height) {
322+
this.cropperCanvas!.style.width = `${Math.min(this.maxSize.width, this.width)}px`;
323+
} else {
324+
this.cropperCanvas!.style.height = `${Math.min(this.maxSize.height, this.height)}px`;
325+
}
315326
}
316327

317328
protected createCropper() {
318329
super.createCropper();
319330

320331
this.dialog!.addEventListener("extra", () => {
321-
this.cropperImage!.$center("contain");
322-
this.cropperSelection!.$reset();
332+
this.centerSelection();
323333
});
324334

325335
// Limit the selection to the min/max size
@@ -336,6 +346,23 @@ class MinMaxImageCropper extends ImageCropper {
336346
}
337347
});
338348
}
349+
350+
protected centerSelection(): void {
351+
this.cropperImage!.$center("contain");
352+
353+
const { width: imageWidth } = this.cropperImage!.getBoundingClientRect();
354+
355+
this.cropperSelection!.$change(
356+
0,
357+
0,
358+
imageWidth,
359+
0,
360+
this.configuration.aspectRatio,
361+
true,
362+
);
363+
this.cropperSelection!.$center();
364+
this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" });
365+
}
339366
}
340367

341368
export async function cropImage(

wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js

Lines changed: 36 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)