Resize the image to the maximum that the browser can display in the dialog.
authorCyperghost <olaf_schmitz_1@t-online.de>
Mon, 16 Dec 2024 11:51:46 +0000 (12:51 +0100)
committerCyperghost <olaf_schmitz_1@t-online.de>
Mon, 16 Dec 2024 11:51:46 +0000 (12:51 +0100)
ts/WoltLabSuite/Core/Component/Image/Cropper.ts
ts/WoltLabSuite/Core/Dom/Util.ts
wcfsetup/install/files/js/WoltLabSuite/Core/Component/Image/Cropper.js
wcfsetup/install/files/js/WoltLabSuite/Core/Dom/Util.js
wcfsetup/install/files/lib/system/file/processor/ImageCropperConfiguration.class.php
wcfsetup/install/files/style/ui/dialog.scss

index 9d9b942ce3efe861f40fcfeab1076a5a22d6b389..0df7481033896ab75dd55b8e9629ebb52778184d 100644 (file)
@@ -47,6 +47,7 @@ abstract class ImageCropper {
   protected dialog?: WoltlabCoreDialogElement;
   protected exif?: ExifUtil.Exif;
   protected orientation?: number;
+  protected cropperCanvasRect?: DOMRect;
   #cropper?: Cropper;
 
   constructor(element: WoltlabCoreFileUploadElement, file: File, configuration: CropperConfiguration) {
@@ -76,6 +77,31 @@ abstract class ImageCropper {
     }
   }
 
+  abstract get minSize(): { width: number; height: number };
+
+  abstract get maxSize(): { width: number; height: number };
+
+  public async loadImage() {
+    const { image, exif } = await this.resizer.loadFile(this.file);
+    this.image = image;
+    this.exif = exif;
+    const tags = await ExifReader.load(this.file);
+    if (tags.Orientation) {
+      switch (tags.Orientation.value) {
+        case 3:
+          this.orientation = 180;
+          break;
+        case 6:
+          this.orientation = 90;
+          break;
+        case 8:
+          this.orientation = 270;
+          break;
+        // Any other rotation is unsupported.
+      }
+    }
+  }
+
   public async showDialog(): Promise<File> {
     this.dialog = dialogFactory().fromElement(this.image!).asPrompt({
       extra: this.getDialogExtra(),
@@ -89,18 +115,21 @@ abstract class ImageCropper {
     };
 
     window.addEventListener("resize", resize, { passive: true });
-    this.dialog.addEventListener(
-      "afterClose",
-      () => {
-        window.removeEventListener("resize", resize);
-      },
-      {
-        once: true,
-      },
-    );
 
     return new Promise<File>((resolve, reject) => {
+      let callReject = true;
+      this.dialog!.addEventListener("afterClose", () => {
+        window.removeEventListener("resize", resize);
+
+        // If the dialog is closed without confirming, reject the promise to trigger a cancel event.
+        if (callReject) {
+          reject();
+        }
+      });
+
       this.dialog!.addEventListener("primary", () => {
+        callReject = false;
+
         void this.getCanvas()
           .then((canvas) => {
             this.resizer
@@ -123,47 +152,23 @@ abstract class ImageCropper {
     });
   }
 
-  protected getCanvas(): Promise<HTMLCanvasElement> {
-    return this.cropperSelection!.$toCanvas();
-  }
-
-  public async loadImage() {
-    const { image, exif } = await this.resizer.loadFile(this.file);
-    this.image = image;
-    this.exif = exif;
-    const tags = await ExifReader.load(this.file);
-    if (tags.Orientation) {
-      switch (tags.Orientation.value) {
-        case 3:
-          this.orientation = 180;
-          break;
-        case 6:
-          this.orientation = 90;
-          break;
-        case 8:
-          this.orientation = 270;
-          break;
-        // Any other rotation is unsupported.
-      }
-    }
-  }
-
-  protected abstract getCropperTemplate(): string;
-
   protected getDialogExtra(): string | undefined {
     return undefined;
   }
 
-  protected setCropperStyle() {
-    this.cropperCanvas!.style.aspectRatio = `${this.width}/${this.height}`;
-
-    if (this.width >= this.height) {
-      this.cropperCanvas!.style.maxHeight = "100%";
-    } else {
-      this.cropperCanvas!.style.maxWidth = "100%";
-    }
+  protected getCanvas(): Promise<HTMLCanvasElement> {
+    // Calculate the size of the image in relation to the window size
+    const selectionRatio = Math.min(
+      this.cropperCanvasRect!.width / this.width,
+      this.cropperCanvasRect!.height / this.height,
+    );
+    const width = this.cropperSelection!.width / selectionRatio;
+    const height = width / this.configuration.aspectRatio;
 
-    this.cropperSelection!.aspectRatio = this.configuration.aspectRatio;
+    return this.cropperSelection!.$toCanvas({
+      width: Math.max(Math.min(Math.floor(width), this.maxSize.width), this.minSize.width),
+      height: Math.max(Math.min(Math.ceil(height), this.maxSize.height), this.minSize.height),
+    });
   }
 
   protected createCropper() {
@@ -200,23 +205,108 @@ abstract class ImageCropper {
         event.preventDefault();
       }
     });
+
+    // Limit the selection to the min/max size
+    this.cropperSelection!.addEventListener("change", (event: CustomEvent) => {
+      const selection = event.detail as Selection;
+      this.cropperCanvasRect = this.cropperCanvas!.getBoundingClientRect();
+
+      const selectionRatio = Math.min(
+        this.cropperCanvasRect.width / this.width,
+        this.cropperCanvasRect.height / this.height,
+      );
+
+      const minWidth = this.minSize.width * selectionRatio;
+      const maxWidth = this.cropperCanvasRect.width;
+      const minHeight = minWidth / this.configuration.aspectRatio;
+      const maxHeight = maxWidth / this.configuration.aspectRatio;
+
+      if (
+        Math.round(selection.width) < minWidth ||
+        Math.round(selection.height) < minHeight ||
+        Math.round(selection.width) > maxWidth ||
+        Math.round(selection.height) > maxHeight
+      ) {
+        event.preventDefault();
+      }
+    });
+  }
+
+  protected setCropperStyle() {
+    this.cropperCanvas!.style.aspectRatio = `${this.width}/${this.height}`;
+
+    this.cropperSelection!.aspectRatio = this.configuration.aspectRatio;
   }
 
   protected centerSelection(): void {
+    // Set to the maximum size
+    this.cropperCanvas!.style.width = `${this.width}px`;
+    this.cropperCanvas!.style.height = `${this.height}px`;
+
+    const dimension = DomUtil.innerDimensions(this.cropperCanvas!.parentElement!);
+    const ratio = Math.min(dimension.width / this.width, dimension.height / this.height);
+
+    this.cropperCanvas!.style.height = `${this.height * ratio}px`;
+    this.cropperCanvas!.style.width = `${this.width * ratio}px`;
+
     this.cropperImage!.$center("contain");
+    this.cropperCanvasRect = this.cropperImage!.getBoundingClientRect();
+
+    const selectionRatio = Math.min(
+      this.cropperCanvasRect.width / this.maxSize.width,
+      this.cropperCanvasRect.height / this.maxSize.height,
+    );
+
+    this.cropperSelection!.$change(
+      0,
+      0,
+      this.maxSize.width * selectionRatio,
+      this.maxSize.height * selectionRatio,
+      this.configuration.aspectRatio,
+      true,
+    );
+
     this.cropperSelection!.$center();
     this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" });
   }
+
+  protected getCropperTemplate(): string {
+    return `<cropper-canvas background scale-step="0.0">
+  <cropper-image skewable scalable translatable rotatable></cropper-image>
+  <cropper-shade hidden></cropper-shade>
+  <cropper-handle action="scale" hidden disabled></cropper-handle>
+  <cropper-selection precise movable resizable outlined>
+    <cropper-grid role="grid" bordered covered></cropper-grid>
+    <cropper-crosshair centered></cropper-crosshair>
+    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
+    <cropper-handle action="n-resize"></cropper-handle>
+    <cropper-handle action="e-resize"></cropper-handle>
+    <cropper-handle action="s-resize"></cropper-handle>
+    <cropper-handle action="w-resize"></cropper-handle>
+    <cropper-handle action="ne-resize"></cropper-handle>
+    <cropper-handle action="nw-resize"></cropper-handle>
+    <cropper-handle action="se-resize"></cropper-handle>
+    <cropper-handle action="sw-resize"></cropper-handle>
+  </cropper-selection>
+</cropper-canvas>`;
+  }
 }
 
 class ExactImageCropper extends ImageCropper {
-  #size?: { width: number; height: number };
+  get minSize() {
+    return this.configuration.sizes[0];
+  }
+
+  get maxSize() {
+    return this.configuration.sizes[this.configuration.sizes.length - 1];
+  }
 
   public async showDialog(): Promise<File> {
     // The image already has the correct size, cropping is not necessary
     if (
-      this.width == this.#size!.width &&
-      this.height == this.#size!.height &&
+      this.configuration.sizes.filter((size) => {
+        return size.width == this.width && size.height == this.height;
+      }).length > 0 &&
       this.image instanceof HTMLCanvasElement
     ) {
       return this.resizer.saveFile(
@@ -232,14 +322,17 @@ class ExactImageCropper extends ImageCropper {
   public async loadImage(): Promise<void> {
     await super.loadImage();
 
-    const timeout = new Promise<File>((resolve) => {
-      window.setTimeout(() => resolve(this.file), 10_000);
-    });
-
-    // resize image to the largest possible size
-    const sizes = this.configuration.sizes.filter((size) => {
-      return size.width <= this.width && size.height <= this.height;
-    });
+    const sizes = this.configuration.sizes
+      .filter((size) => {
+        return size.width <= this.width && size.height <= this.height;
+      })
+      .sort((a, b) => {
+        if (this.configuration.aspectRatio >= 1) {
+          return a.width - b.width;
+        } else {
+          return a.height - b.height;
+        }
+      });
 
     if (sizes.length === 0) {
       const smallestSize =
@@ -252,43 +345,11 @@ class ExactImageCropper extends ImageCropper {
       );
     }
 
-    this.#size = sizes[sizes.length - 1];
-    this.image = await this.resizer.resize(
-      this.image as HTMLImageElement,
-      this.width >= this.height ? this.width : this.#size.width,
-      this.height > this.width ? this.height : this.#size.height,
-      this.resizer.quality,
-      true,
-      timeout,
-    );
-  }
-
-  protected getCropperTemplate(): string {
-    return `<cropper-canvas background>
-  <cropper-image rotatable></cropper-image>
-  <cropper-shade hidden></cropper-shade>
-  <cropper-selection movable outlined keyboard>
-    <cropper-grid role="grid" bordered covered></cropper-grid>
-    <cropper-crosshair centered></cropper-crosshair>
-    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
-  </cropper-selection>
-</cropper-canvas>`;
-  }
-
-  protected setCropperStyle() {
-    super.setCropperStyle();
-
-    this.cropperSelection!.width = this.#size!.width;
-    this.cropperSelection!.height = this.#size!.height;
-
-    this.cropperCanvas!.style.width = `${this.width}px`;
-    this.cropperCanvas!.style.height = `${this.height}px`;
-    this.cropperSelection!.style.removeProperty("aspectRatio");
+    this.configuration.sizes = sizes;
   }
 }
 
 class MinMaxImageCropper extends ImageCropper {
-  #cropperCanvasRect?: DOMRect;
   constructor(element: WoltlabCoreFileUploadElement, file: File, configuration: CropperConfiguration) {
     super(element, file, configuration);
     if (configuration.sizes.length !== 2) {
@@ -311,7 +372,7 @@ class MinMaxImageCropper extends ImageCropper {
   public async loadImage(): Promise<void> {
     await super.loadImage();
 
-    if (this.image!.width < this.minSize.width || this.image!.height < this.minSize.height) {
+    if (this.width < this.minSize.width || this.height < this.minSize.height) {
       throw new Error(
         getPhrase("wcf.upload.error.image.tooSmall", {
           width: this.minSize.width,
@@ -321,105 +382,12 @@ class MinMaxImageCropper extends ImageCropper {
     }
   }
 
-  protected getCropperTemplate(): string {
-    return `<cropper-canvas background scale-step="0.0">
-  <cropper-image skewable scalable translatable rotatable></cropper-image>
-  <cropper-shade hidden></cropper-shade>
-  <cropper-handle action="scale" hidden disabled></cropper-handle>
-  <cropper-selection precise movable resizable outlined>
-    <cropper-grid role="grid" bordered covered></cropper-grid>
-    <cropper-crosshair centered></cropper-crosshair>
-    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
-    <cropper-handle action="n-resize"></cropper-handle>
-    <cropper-handle action="e-resize"></cropper-handle>
-    <cropper-handle action="s-resize"></cropper-handle>
-    <cropper-handle action="w-resize"></cropper-handle>
-    <cropper-handle action="ne-resize"></cropper-handle>
-    <cropper-handle action="nw-resize"></cropper-handle>
-    <cropper-handle action="se-resize"></cropper-handle>
-    <cropper-handle action="sw-resize"></cropper-handle>
-  </cropper-selection>
-</cropper-canvas>`;
-  }
-
   protected createCropper() {
     super.createCropper();
 
     this.dialog!.addEventListener("extra", () => {
       this.centerSelection();
     });
-
-    // Limit the selection to the min/max size
-    this.cropperSelection!.addEventListener("change", (event: CustomEvent) => {
-      const selection = event.detail as Selection;
-      this.#cropperCanvasRect = this.cropperCanvas!.getBoundingClientRect();
-
-      const maxImageWidth = Math.min(this.image!.width, this.maxSize.width);
-      const maxImageHeight = Math.min(this.image!.height, this.maxSize.height);
-      const selectionRatio = Math.min(
-        this.#cropperCanvasRect.width / maxImageWidth,
-        this.#cropperCanvasRect.height / maxImageHeight,
-      );
-
-      const minWidth = this.minSize.width * selectionRatio;
-      const maxWidth = this.maxSize.width * selectionRatio;
-      const minHeight = minWidth / this.configuration.aspectRatio;
-      const maxHeight = maxWidth / this.configuration.aspectRatio;
-
-      if (
-        Math.round(selection.width) < minWidth ||
-        Math.round(selection.height) < minHeight ||
-        Math.round(selection.width) > maxWidth ||
-        Math.round(selection.height) > maxHeight
-      ) {
-        event.preventDefault();
-      }
-    });
-  }
-
-  protected getCanvas(): Promise<HTMLCanvasElement> {
-    // Calculate the size of the image in relation to the window size
-    const maxImageWidth = Math.min(this.image!.width, this.maxSize.width);
-    const widthRatio = this.#cropperCanvasRect!.width / maxImageWidth;
-    const width = this.cropperSelection!.width / widthRatio;
-    const height = width / this.configuration.aspectRatio;
-
-    return this.cropperSelection!.$toCanvas({
-      width: Math.max(Math.min(Math.ceil(width), this.maxSize.width), this.minSize.width),
-      height: Math.max(Math.min(Math.ceil(height), this.maxSize.height), this.minSize.height),
-    });
-  }
-
-  protected centerSelection(): void {
-    // Reset to get the maximum available height and width
-    this.cropperCanvas!.style.height = "";
-    this.cropperCanvas!.style.width = "";
-
-    const dimension = DomUtil.innerDimensions(this.cropperCanvas!.parentElement!);
-    const ratio = Math.min(dimension.width / this.image!.width, dimension.height / this.image!.height);
-
-    this.cropperCanvas!.style.height = `${this.image!.height * ratio}px`;
-    this.cropperCanvas!.style.width = `${this.image!.width * ratio}px`;
-
-    this.cropperImage!.$center("contain");
-    this.#cropperCanvasRect = this.cropperImage!.getBoundingClientRect();
-
-    const selectionRatio = Math.min(
-      this.#cropperCanvasRect.width / this.maxSize.width,
-      this.#cropperCanvasRect.height / this.maxSize.height,
-    );
-
-    this.cropperSelection!.$change(
-      0,
-      0,
-      this.maxSize.width * selectionRatio,
-      this.maxSize.height * selectionRatio,
-      this.configuration.aspectRatio,
-      true,
-    );
-
-    this.cropperSelection!.$center();
-    this.cropperSelection!.scrollIntoView({ block: "center", inline: "center" });
   }
 }
 
index 66c3f53837e9e04c75e6945e99f81a49d151dffe..6f1052d80aa9b9b4d9d5362402047c1885234e6a 100644 (file)
@@ -103,7 +103,7 @@ const DomUtil = {
     styles = styles || window.getComputedStyle(element);
 
     let height = element.clientHeight;
-    height += ~~styles.paddingTop + ~~styles.paddingBottom;
+    height -= ~~styles.paddingTop + ~~styles.paddingBottom;
 
     return height;
   },
@@ -115,7 +115,7 @@ const DomUtil = {
     styles = styles || window.getComputedStyle(element);
 
     let width = element.clientWidth;
-    width += ~~styles.paddingLeft + ~~styles.paddingRight;
+    width -= ~~parseInt(styles.paddingLeft) + ~~parseInt(styles.paddingRight);
 
     return width;
   },
index 60bd94b1b2866782b4a61837597c6d1b8f31f922..046b13b6cdfd201a9bb8ebbf91bce81dd7dfbbd4 100644 (file)
@@ -32,6 +32,7 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
         dialog;
         exif;
         orientation;
+        cropperCanvasRect;
         #cropper;
         constructor(element, file, configuration) {
             this.configuration = configuration;
@@ -57,6 +58,26 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                     return this.image.height;
             }
         }
+        async loadImage() {
+            const { image, exif } = await this.resizer.loadFile(this.file);
+            this.image = image;
+            this.exif = exif;
+            const tags = await exifreader_1.default.load(this.file);
+            if (tags.Orientation) {
+                switch (tags.Orientation.value) {
+                    case 3:
+                        this.orientation = 180;
+                        break;
+                    case 6:
+                        this.orientation = 90;
+                        break;
+                    case 8:
+                        this.orientation = 270;
+                        break;
+                    // Any other rotation is unsupported.
+                }
+            }
+        }
         async showDialog() {
             this.dialog = (0, Dialog_1.dialogFactory)().fromElement(this.image).asPrompt({
                 extra: this.getDialogExtra(),
@@ -67,13 +88,17 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                 this.centerSelection();
             };
             window.addEventListener("resize", resize, { passive: true });
-            this.dialog.addEventListener("afterClose", () => {
-                window.removeEventListener("resize", resize);
-            }, {
-                once: true,
-            });
             return new Promise((resolve, reject) => {
+                let callReject = true;
+                this.dialog.addEventListener("afterClose", () => {
+                    window.removeEventListener("resize", resize);
+                    // If the dialog is closed without confirming, reject the promise to trigger a cancel event.
+                    if (callReject) {
+                        reject();
+                    }
+                });
                 this.dialog.addEventListener("primary", () => {
+                    callReject = false;
                     void this.getCanvas()
                         .then((canvas) => {
                         this.resizer
@@ -91,41 +116,18 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                 });
             });
         }
-        getCanvas() {
-            return this.cropperSelection.$toCanvas();
-        }
-        async loadImage() {
-            const { image, exif } = await this.resizer.loadFile(this.file);
-            this.image = image;
-            this.exif = exif;
-            const tags = await exifreader_1.default.load(this.file);
-            if (tags.Orientation) {
-                switch (tags.Orientation.value) {
-                    case 3:
-                        this.orientation = 180;
-                        break;
-                    case 6:
-                        this.orientation = 90;
-                        break;
-                    case 8:
-                        this.orientation = 270;
-                        break;
-                    // Any other rotation is unsupported.
-                }
-            }
-        }
         getDialogExtra() {
             return undefined;
         }
-        setCropperStyle() {
-            this.cropperCanvas.style.aspectRatio = `${this.width}/${this.height}`;
-            if (this.width >= this.height) {
-                this.cropperCanvas.style.maxHeight = "100%";
-            }
-            else {
-                this.cropperCanvas.style.maxWidth = "100%";
-            }
-            this.cropperSelection.aspectRatio = this.configuration.aspectRatio;
+        getCanvas() {
+            // Calculate the size of the image in relation to the window size
+            const selectionRatio = Math.min(this.cropperCanvasRect.width / this.width, this.cropperCanvasRect.height / this.height);
+            const width = this.cropperSelection.width / selectionRatio;
+            const height = width / this.configuration.aspectRatio;
+            return this.cropperSelection.$toCanvas({
+                width: Math.max(Math.min(Math.floor(width), this.maxSize.width), this.minSize.width),
+                height: Math.max(Math.min(Math.ceil(height), this.maxSize.height), this.minSize.height),
+            });
         }
         createCropper() {
             this.#cropper = new cropperjs_1.default(this.image, {
@@ -154,19 +156,75 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                     event.preventDefault();
                 }
             });
+            // Limit the selection to the min/max size
+            this.cropperSelection.addEventListener("change", (event) => {
+                const selection = event.detail;
+                this.cropperCanvasRect = this.cropperCanvas.getBoundingClientRect();
+                const selectionRatio = Math.min(this.cropperCanvasRect.width / this.width, this.cropperCanvasRect.height / this.height);
+                const minWidth = this.minSize.width * selectionRatio;
+                const maxWidth = this.cropperCanvasRect.width;
+                const minHeight = minWidth / this.configuration.aspectRatio;
+                const maxHeight = maxWidth / this.configuration.aspectRatio;
+                if (Math.round(selection.width) < minWidth ||
+                    Math.round(selection.height) < minHeight ||
+                    Math.round(selection.width) > maxWidth ||
+                    Math.round(selection.height) > maxHeight) {
+                    event.preventDefault();
+                }
+            });
+        }
+        setCropperStyle() {
+            this.cropperCanvas.style.aspectRatio = `${this.width}/${this.height}`;
+            this.cropperSelection.aspectRatio = this.configuration.aspectRatio;
         }
         centerSelection() {
+            // Set to the maximum size
+            this.cropperCanvas.style.width = `${this.width}px`;
+            this.cropperCanvas.style.height = `${this.height}px`;
+            const dimension = Util_1.default.innerDimensions(this.cropperCanvas.parentElement);
+            const ratio = Math.min(dimension.width / this.width, dimension.height / this.height);
+            this.cropperCanvas.style.height = `${this.height * ratio}px`;
+            this.cropperCanvas.style.width = `${this.width * ratio}px`;
             this.cropperImage.$center("contain");
+            this.cropperCanvasRect = this.cropperImage.getBoundingClientRect();
+            const selectionRatio = Math.min(this.cropperCanvasRect.width / this.maxSize.width, this.cropperCanvasRect.height / this.maxSize.height);
+            this.cropperSelection.$change(0, 0, this.maxSize.width * selectionRatio, this.maxSize.height * selectionRatio, this.configuration.aspectRatio, true);
             this.cropperSelection.$center();
             this.cropperSelection.scrollIntoView({ block: "center", inline: "center" });
         }
+        getCropperTemplate() {
+            return `<cropper-canvas background scale-step="0.0">
+  <cropper-image skewable scalable translatable rotatable></cropper-image>
+  <cropper-shade hidden></cropper-shade>
+  <cropper-handle action="scale" hidden disabled></cropper-handle>
+  <cropper-selection precise movable resizable outlined>
+    <cropper-grid role="grid" bordered covered></cropper-grid>
+    <cropper-crosshair centered></cropper-crosshair>
+    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
+    <cropper-handle action="n-resize"></cropper-handle>
+    <cropper-handle action="e-resize"></cropper-handle>
+    <cropper-handle action="s-resize"></cropper-handle>
+    <cropper-handle action="w-resize"></cropper-handle>
+    <cropper-handle action="ne-resize"></cropper-handle>
+    <cropper-handle action="nw-resize"></cropper-handle>
+    <cropper-handle action="se-resize"></cropper-handle>
+    <cropper-handle action="sw-resize"></cropper-handle>
+  </cropper-selection>
+</cropper-canvas>`;
+        }
     }
     class ExactImageCropper extends ImageCropper {
-        #size;
+        get minSize() {
+            return this.configuration.sizes[0];
+        }
+        get maxSize() {
+            return this.configuration.sizes[this.configuration.sizes.length - 1];
+        }
         async showDialog() {
             // The image already has the correct size, cropping is not necessary
-            if (this.width == this.#size.width &&
-                this.height == this.#size.height &&
+            if (this.configuration.sizes.filter((size) => {
+                return size.width == this.width && size.height == this.height;
+            }).length > 0 &&
                 this.image instanceof HTMLCanvasElement) {
                 return this.resizer.saveFile({ exif: this.orientation ? undefined : this.exif, image: this.image }, this.file.name, this.file.type);
             }
@@ -174,12 +232,17 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
         }
         async loadImage() {
             await super.loadImage();
-            const timeout = new Promise((resolve) => {
-                window.setTimeout(() => resolve(this.file), 10_000);
-            });
-            // resize image to the largest possible size
-            const sizes = this.configuration.sizes.filter((size) => {
+            const sizes = this.configuration.sizes
+                .filter((size) => {
                 return size.width <= this.width && size.height <= this.height;
+            })
+                .sort((a, b) => {
+                if (this.configuration.aspectRatio >= 1) {
+                    return a.width - b.width;
+                }
+                else {
+                    return a.height - b.height;
+                }
             });
             if (sizes.length === 0) {
                 const smallestSize = this.configuration.sizes.length > 1 ? this.configuration.sizes[this.configuration.sizes.length - 1] : undefined;
@@ -188,31 +251,10 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
                     height: smallestSize?.height,
                 }));
             }
-            this.#size = sizes[sizes.length - 1];
-            this.image = await this.resizer.resize(this.image, this.width >= this.height ? this.width : this.#size.width, this.height > this.width ? this.height : this.#size.height, this.resizer.quality, true, timeout);
-        }
-        getCropperTemplate() {
-            return `<cropper-canvas background>
-  <cropper-image rotatable></cropper-image>
-  <cropper-shade hidden></cropper-shade>
-  <cropper-selection movable outlined keyboard>
-    <cropper-grid role="grid" bordered covered></cropper-grid>
-    <cropper-crosshair centered></cropper-crosshair>
-    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
-  </cropper-selection>
-</cropper-canvas>`;
-        }
-        setCropperStyle() {
-            super.setCropperStyle();
-            this.cropperSelection.width = this.#size.width;
-            this.cropperSelection.height = this.#size.height;
-            this.cropperCanvas.style.width = `${this.width}px`;
-            this.cropperCanvas.style.height = `${this.height}px`;
-            this.cropperSelection.style.removeProperty("aspectRatio");
+            this.configuration.sizes = sizes;
         }
     }
     class MinMaxImageCropper extends ImageCropper {
-        #cropperCanvasRect;
         constructor(element, file, configuration) {
             super(element, file, configuration);
             if (configuration.sizes.length !== 2) {
@@ -230,82 +272,18 @@ define(["require", "exports", "tslib", "WoltLabSuite/Core/Image/Resizer", "WoltL
         }
         async loadImage() {
             await super.loadImage();
-            if (this.image.width < this.minSize.width || this.image.height < this.minSize.height) {
+            if (this.width < this.minSize.width || this.height < this.minSize.height) {
                 throw new Error((0, Language_1.getPhrase)("wcf.upload.error.image.tooSmall", {
                     width: this.minSize.width,
                     height: this.minSize.height,
                 }));
             }
         }
-        getCropperTemplate() {
-            return `<cropper-canvas background scale-step="0.0">
-  <cropper-image skewable scalable translatable rotatable></cropper-image>
-  <cropper-shade hidden></cropper-shade>
-  <cropper-handle action="scale" hidden disabled></cropper-handle>
-  <cropper-selection precise movable resizable outlined>
-    <cropper-grid role="grid" bordered covered></cropper-grid>
-    <cropper-crosshair centered></cropper-crosshair>
-    <cropper-handle action="move" theme-color="rgba(255, 255, 255, 0.35)"></cropper-handle>
-    <cropper-handle action="n-resize"></cropper-handle>
-    <cropper-handle action="e-resize"></cropper-handle>
-    <cropper-handle action="s-resize"></cropper-handle>
-    <cropper-handle action="w-resize"></cropper-handle>
-    <cropper-handle action="ne-resize"></cropper-handle>
-    <cropper-handle action="nw-resize"></cropper-handle>
-    <cropper-handle action="se-resize"></cropper-handle>
-    <cropper-handle action="sw-resize"></cropper-handle>
-  </cropper-selection>
-</cropper-canvas>`;
-        }
         createCropper() {
             super.createCropper();
             this.dialog.addEventListener("extra", () => {
                 this.centerSelection();
             });
-            // Limit the selection to the min/max size
-            this.cropperSelection.addEventListener("change", (event) => {
-                const selection = event.detail;
-                this.#cropperCanvasRect = this.cropperCanvas.getBoundingClientRect();
-                const maxImageWidth = Math.min(this.image.width, this.maxSize.width);
-                const maxImageHeight = Math.min(this.image.height, this.maxSize.height);
-                const selectionRatio = Math.min(this.#cropperCanvasRect.width / maxImageWidth, this.#cropperCanvasRect.height / maxImageHeight);
-                const minWidth = this.minSize.width * selectionRatio;
-                const maxWidth = this.maxSize.width * selectionRatio;
-                const minHeight = minWidth / this.configuration.aspectRatio;
-                const maxHeight = maxWidth / this.configuration.aspectRatio;
-                if (Math.round(selection.width) < minWidth ||
-                    Math.round(selection.height) < minHeight ||
-                    Math.round(selection.width) > maxWidth ||
-                    Math.round(selection.height) > maxHeight) {
-                    event.preventDefault();
-                }
-            });
-        }
-        getCanvas() {
-            // Calculate the size of the image in relation to the window size
-            const maxImageWidth = Math.min(this.image.width, this.maxSize.width);
-            const widthRatio = this.#cropperCanvasRect.width / maxImageWidth;
-            const width = this.cropperSelection.width / widthRatio;
-            const height = width / this.configuration.aspectRatio;
-            return this.cropperSelection.$toCanvas({
-                width: Math.max(Math.min(Math.ceil(width), this.maxSize.width), this.minSize.width),
-                height: Math.max(Math.min(Math.ceil(height), this.maxSize.height), this.minSize.height),
-            });
-        }
-        centerSelection() {
-            // Reset to get the maximum available height and width
-            this.cropperCanvas.style.height = "";
-            this.cropperCanvas.style.width = "";
-            const dimension = Util_1.default.innerDimensions(this.cropperCanvas.parentElement);
-            const ratio = Math.min(dimension.width / this.image.width, dimension.height / this.image.height);
-            this.cropperCanvas.style.height = `${this.image.height * ratio}px`;
-            this.cropperCanvas.style.width = `${this.image.width * ratio}px`;
-            this.cropperImage.$center("contain");
-            this.#cropperCanvasRect = this.cropperImage.getBoundingClientRect();
-            const selectionRatio = Math.min(this.#cropperCanvasRect.width / this.maxSize.width, this.#cropperCanvasRect.height / this.maxSize.height);
-            this.cropperSelection.$change(0, 0, this.maxSize.width * selectionRatio, this.maxSize.height * selectionRatio, this.configuration.aspectRatio, true);
-            this.cropperSelection.$center();
-            this.cropperSelection.scrollIntoView({ block: "center", inline: "center" });
         }
     }
     async function cropImage(element, file, configuration) {
index f981c4f49a88b694a35b7ac56c8b15fae1d825dc..736f5015fa74c677e79a282a4431de67def8dfab 100644 (file)
@@ -87,7 +87,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo
         innerHeight(element, styles) {
             styles = styles || window.getComputedStyle(element);
             let height = element.clientHeight;
-            height += ~~styles.paddingTop + ~~styles.paddingBottom;
+            height -= ~~styles.paddingTop + ~~styles.paddingBottom;
             return height;
         },
         /**
@@ -96,7 +96,7 @@ define(["require", "exports", "tslib", "../StringUtil"], function (require, expo
         innerWidth(element, styles) {
             styles = styles || window.getComputedStyle(element);
             let width = element.clientWidth;
-            width += ~~styles.paddingLeft + ~~styles.paddingRight;
+            width -= ~~parseInt(styles.paddingLeft) + ~~parseInt(styles.paddingRight);
             return width;
         },
         /**
index 8cb30fe0844f73d8384e371ccc778ce218d03fbb..73b928e0a42c2a61ecf317f4110266f02a94e384 100644 (file)
@@ -77,11 +77,12 @@ final class ImageCropperConfiguration implements \JsonSerializable
      * - Image is 100x200
      *   - Image is rejected
      * - Image is 200x150
-     *   - Image is resized to 170x128
+     *   - Uploaded image is 128x128
      * - Image is 150x200
-     *   - Image is resized to 128x170
+     *   - Uploaded image is 128x128
      * - Image is 300x300
-     *   - Image is resized to 256x256
+     *   - Uploaded can image is 128x128 or 256x256, depending on cropping selection from the user
+     * - Image is 256x256
      *   - The image is uploaded directly without displaying the cropping dialog
      */
     public static function forExact(ImageCropSize ...$sizes): self
index cca59d6c5bbaf37a2adc968c73932cdb1455aaca..627afc6bdaf59ca940826c49d551eeadea55dacd 100644 (file)
@@ -469,6 +469,9 @@ html[data-color-scheme="dark"] .dialog::backdrop {
 .dialog cropper-canvas {
        margin-left: auto;
        margin-right: auto;
+       max-height: 100%;
+       max-width: 100%;
+
        /* overwrites the default values of `min-height: 100px` and `min-width: 200px` */
        min-height: 1px;
        min-width: 1px;