[FIX] web_editor: scroll to invisible images on crop

Steps to reproduce:

- Go to a website page (in "edit" mode) > Add a "Text Image" block > Add
other blocks before and after the snippet so you can scroll the page
content.
- Scroll in a way that makes only a part of the image visible.
- Select the image and click on the "Crop" button > The crop widget is
applied to the image in its current position (partially visible), which
makes it impossible to crop it correctly.

The goal of this commit is to prevent the issue described above by
simply scrolling to a position that allows to correctly edit the image
before applying the crop widget and also allowing the widget element to
scroll when trying to crop an image that overflows the current viewport.

task-3420186

closes odoo/odoo#155094

X-original-commit: 8ef34c15336c6938f40c15dbcc64c025b24d5808
Signed-off-by: Robin Lejeune (role) <role@odoo.com>
This commit is contained in:
Mehdi Outagant (MOU)
2024-03-04 09:52:01 +00:00
parent 6aab29c97c
commit 39ba96b0ac
2 changed files with 38 additions and 0 deletions
@@ -12,6 +12,7 @@ import {
markup,
} from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
import dom from "@web/legacy/js/core/dom";
export class ImageCrop extends Component {
static template = 'web_editor.ImageCrop';
@@ -147,6 +148,7 @@ export class ImageCrop extends Component {
}
const $cropperWrapper = this.$('.o_we_cropper_wrapper');
await this._scrollToInvisibleImage();
// Replacing the src with the original's so that the layout is correct.
await loadImage(this.originalSrc, this.media);
this.$cropperImage = this.$('.o_we_cropper_img');
@@ -221,6 +223,41 @@ export class ImageCrop extends Component {
this.$cropperImage.cropper('clear');
this.$cropperImage.cropper('crop');
}
/**
* Make sure the targeted image is in the visible viewport before crop.
*
* @private
*/
async _scrollToInvisibleImage() {
const rect = this.media.getBoundingClientRect();
const viewportTop = this.document.documentElement.scrollTop || 0;
const viewportBottom = viewportTop + window.innerHeight;
const closestScrollable = el => {
if (!el) {
return null;
}
if (el.scrollHeight > el.clientHeight) {
return $(el);
} else {
return closestScrollable(el.parentElement);
}
};
// Give priority to the closest scrollable element (e.g. for images in
// HTML fields, the element to scroll is different from the document's
// scrolling element).
const $scrollable = closestScrollable(this.media);
// The image must be in a position that allows access to it and its crop
// options buttons. Otherwise, the crop widget container can be scrolled
// to allow editing.
if (rect.top < viewportTop || viewportBottom - rect.bottom < 100) {
await dom.scrollTo(this.media, {
easing: "linear",
duration: 500,
...($scrollable && { $scrollable }),
});
}
}
//--------------------------------------------------------------------------
// Handlers
@@ -605,6 +605,7 @@ img.o_we_selected_image {
@include o-position-absolute(0, 0, 0, 0);
/* This value must be higher than dialog z-index in bootstrap */
z-index: 1056;
overflow: auto;
.o_we_cropper_wrapper {
position: absolute;