Skip to content

Commit

Permalink
[IMP] web_editor, *: improve toggle and d&d in grid mode for the images
Browse files Browse the repository at this point in the history
*: website

In grid mode, the images take the whole space in their column so they
always have the same size as them. However, if text was added in the
column (before or after toggle), it always looked like it was
overflowing and the overlay would not cover it even after resizing.

Also, since these images have the value of the `object-fit` property
set to `cover`, the images are cut if they are too big for the column.
This is problematic if the image has a shape because the shape is cut
too, ruining the visual effect.

This commit solves these issues by distinguishing the images that are
alone in their column from those that are not. An image is considered
alone if it is a direct child of the column and if there is no text in
it (the line breaks and empty lines are excluded). Now, only this kind
of images, if they have no shape, take the whole space in the column
and have `object-fit` set to `cover`; the other ones behave like the
columns in normal mode.

This distinction also allows to block the edition of the columns
containing only an image. Indeed, adding text or dropping inner content
in them had the same overflowing effect.

task-2973198

X-original-commit: e9c7e02
Part-of: odoo#102525
  • Loading branch information
sobo-odoo authored and qsm-odoo committed Oct 6, 2022
1 parent 339f963 commit 1dff079
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 12 deletions.
69 changes: 62 additions & 7 deletions addons/web_editor/static/src/js/common/grid_layout_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,26 +141,36 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
const allBackgroundColor = [...columnEls].every(columnEl => columnEl.classList.contains('o_cc'));

for (const columnEl of columnEls) {
// Finding out if the images are alone in their column.
let isImageColumn = _checkIfImageColumn(columnEl);
const imageEl = columnEl.querySelector('img');

// Placing the column.
const style = window.getComputedStyle(columnEl);
// Horizontal placement.
const columnLeft = columnEl.offsetLeft;
const columnLeft = isImageColumn ? imageEl.offsetLeft : columnEl.offsetLeft;
// Getting the width of the column.
const paddingLeft = parseFloat(style.paddingLeft);
const width = parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
const columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
const width = isImageColumn ? parseFloat(imageEl.scrollWidth)
: parseFloat(columnEl.scrollWidth) - (allBackgroundColor ? 0 : 2 * paddingLeft);
let columnSpan = Math.round((width + columnGap) / (columnSize + columnGap));
if (columnSpan < 1) {
columnSpan = 1;
}
const columnStart = Math.round(columnLeft / (columnSize + columnGap)) + 1;
const columnEnd = columnStart + columnSpan;

// Vertical placement.
const columnTop = columnEl.offsetTop;
const columnTop = isImageColumn ? imageEl.offsetTop : columnEl.offsetTop;
// Getting the top and bottom paddings and computing the row offset.
const paddingTop = parseFloat(style.paddingTop);
const paddingBottom = parseFloat(style.paddingBottom);
const rowOffsetTop = Math.floor((paddingTop + rowGap) / (rowSize + rowGap));
// Getting the height of the column.
const height = parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
const height = isImageColumn ? parseFloat(imageEl.scrollHeight)
: parseFloat(columnEl.scrollHeight) - (allBackgroundColor ? 0 : paddingTop + paddingBottom);
const rowSpan = Math.ceil((height + rowGap) / (rowSize + rowGap));
const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor ? 0 : rowOffsetTop);
const rowStart = Math.round(columnTop / (rowSize + rowGap)) + 1 + (allBackgroundColor || isImageColumn ? 0 : rowOffsetTop);
const rowEnd = rowStart + rowSpan;

columnEl.style.gridArea = `${rowStart} / ${columnStart} / ${rowEnd} / ${columnEnd}`;
Expand All @@ -183,6 +193,7 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {

maxRowEnd = Math.max(rowEnd, maxRowEnd);
columnSpans.push(columnSpan);
imageColumns.push(isImageColumn);
}

// If all the columns have a background color, set their padding to the
Expand All @@ -196,14 +207,19 @@ function _placeColumns(columnEls, rowSize, rowGap, columnSize, columnGap) {
rowEl.style.setProperty('--grid-item-padding-x', paddingX);
}

// Removing padding and offset classes.
for (const [i, columnEl] of [...columnEls].entries()) {
// Removing padding and offset classes.
const regex = /^(pt|pb|col-|offset-)/;
const toRemove = [...columnEl.classList].filter(c => {
return regex.test(c);
});
columnEl.classList.remove(...toRemove);
columnEl.classList.add('col-lg-' + columnSpans[i]);

// If the column only has an image, convert it.
if (imageColumns[i]) {
_convertImageColumn(columnEl);
}
}

return maxRowEnd;
Expand All @@ -223,3 +239,42 @@ export function _reloadLazyImages(columnEl) {
imageEl.src = src;
}
}
/**
* Checks whether the column only contains an image or not. An image is
* considered alone if the column only contains empty textnodes and line breaks
* in addition to the image.
*
* @private
* @param {Element} columnEl
* @returns {Boolean}
*/
export function _checkIfImageColumn(columnEl) {
let isImageColumn = false;
const imageEls = columnEl.querySelectorAll(':scope > img');
const columnChildrenEls = [...columnEl.children].filter(el => el.nodeName !== 'BR');
if (imageEls.length === 1 && columnChildrenEls.length === 1) {
// If there is only one image and if this image is the only "real"
// child of the column, we need to check if there is text in it.
const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
const areTextNodesEmpty = [...textNodeEls].every(textNodeEl => textNodeEl.nodeValue.trim() === '');
isImageColumn = areTextNodesEmpty;
}
return isImageColumn;
}
/**
* Removes the line breaks and textnodes of the column, adds the grid class and
* sets the image width to default so it can be displayed as expected. Also
* blocks the edition of the column.
*
* @private
* @param {Element} columnEl a column containing only an image.
*/
function _convertImageColumn(columnEl) {
columnEl.querySelectorAll('br').forEach(el => el.remove());
const textNodeEls = [...columnEl.childNodes].filter(el => el.nodeType === Node.TEXT_NODE);
textNodeEls.forEach(el => el.remove());
const imageEl = columnEl.querySelector('img');
columnEl.classList.add('o_grid_item_image');
columnEl.contentEditable = false;
imageEl.style.removeProperty('width');
}
14 changes: 14 additions & 0 deletions addons/web_editor/static/src/js/editor/snippets.editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,12 @@ var SnippetEditor = Widget.extend({
// a flickering when not needed.
this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', postAnimationCover);

// Set the `contenteditable` attribute to false for all the columns
// having the class `o_grid_item_image` (as it is removed when leaving
// the edit mode).
const imageColumnEls = this.$target[0].querySelectorAll('.o_grid_item_image');
imageColumnEls.forEach(imageColumnEl => imageColumnEl.contentEditable = false);

return Promise.all(defs).then(() => {
this.__isStartedResolveFunc(this);
});
Expand Down Expand Up @@ -1177,6 +1183,10 @@ var SnippetEditor = Widget.extend({
// Case when dropping a grid item in a non-grid dropzone.
this.$target[0].classList.remove('o_grid_item');
this.$target[0].style.removeProperty('grid-area');
if (this.$target[0].classList.contains('o_grid_item_image')) {
this.$target[0].classList.remove('o_grid_item_image');
this.$target[0].removeAttribute('contentEditable');
}
}

// TODO lot of this is duplicated code of the d&d feature of snippets
Expand All @@ -1202,6 +1212,10 @@ var SnippetEditor = Widget.extend({
// Case when a column is dropped near a non-grid dropzone.
this.$target[0].classList.remove('o_grid_item');
this.$target[0].style.removeProperty('z-index');
if (this.$target[0].classList.contains('o_grid_item_image')) {
this.$target[0].classList.remove('o_grid_item_image');
this.$target[0].removeAttribute('contentEditable');
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion addons/web_editor/static/src/js/editor/snippets.options.js
Original file line number Diff line number Diff line change
Expand Up @@ -4965,7 +4965,8 @@ registry.layout_column = SnippetOptionWidget.extend({

if (elementType === 'image') {
// Set the columns properties.
newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6');
newColumnEl.classList.add('col-lg-6', 'g-col-lg-6', 'g-height-6', 'o_grid_item_image');
newColumnEl.contentEditable = false;
numberColumns = 6;
numberRows = 6;

Expand Down Expand Up @@ -5109,6 +5110,10 @@ registry.layout_column = SnippetOptionWidget.extend({
columnEl.classList.remove('o_grid_item');
columnEl.style.removeProperty('grid-area');
columnEl.style.removeProperty('z-index');
if (columnEl.classList.contains('o_grid_item_image')) {
columnEl.classList.remove('o_grid_item_image');
columnEl.removeAttribute('contentEditable');
}
}
// Removing the grid properties.
delete rowEl.dataset.rowCount;
Expand Down
6 changes: 3 additions & 3 deletions addons/web_editor/static/src/scss/web_editor.frontend.scss
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,9 @@
.o_grid_item {
word-break: break-word;

> img {
width: 100%;
height: 100%;
&.o_grid_item_image > img:not([data-shape]) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important;
}
}
2 changes: 1 addition & 1 deletion addons/website/views/snippets/snippets.xml
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@
<t t-set="so_content_addition_selector" t-translation="off">blockquote, .s_card:not(.s_timeline_card), .s_alert, .o_facebook_page, .s_share, .s_social_media, .s_rating, .s_hr, .s_google_map, .s_map, .s_countdown, .s_chart, .s_text_highlight, .s_progress_bar, .s_badge, .s_embed_code, .s_donation, .s_add_to_cart</t>
<div id="so_content_addition"
t-att-data-selector="so_content_addition_selector"
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div > img, #{so_content_addition_selector}"
t-attf-data-drop-near="p, h1, h2, h3, ul, ol, .row > div:not(.o_grid_item_image) > img, #{so_content_addition_selector}"
data-drop-in=".content, nav"/>

<div data-js="SnippetSave"
Expand Down

0 comments on commit 1dff079

Please sign in to comment.