diff --git a/addons/web_editor/static/src/js/common/grid_layout_utils.js b/addons/web_editor/static/src/js/common/grid_layout_utils.js index 80bac5ae5b9..b940cf14bd1 100644 --- a/addons/web_editor/static/src/js/common/grid_layout_utils.js +++ b/addons/web_editor/static/src/js/common/grid_layout_utils.js @@ -251,6 +251,35 @@ export function _reloadLazyImages(columnEl) { imageEl.src = src; } } +/** + * Computes the column and row spans of the column thanks to its width and + * height and returns them. Also adds the grid classes to the column. + * + * @private + * @param {Element} rowEl + * @param {Element} columnEl + * @param {Number} columnWidth the width in pixels of the column. + * @param {Number} columnHeight the height in pixels of the column. + * @returns {Object} + */ +export function _convertColumnToGrid(rowEl, columnEl, columnWidth, columnHeight) { + // First, checking if the column only contains an image and if it is the + // case, converting it. + if (_checkIfImageColumn(columnEl)) { + _convertImageColumn(columnEl); + } + + // Computing the column and row spans. + const gridProp = _getGridProperties(rowEl); + const columnColCount = Math.round((columnWidth + gridProp.columnGap) / (gridProp.columnSize + gridProp.columnGap)); + const columnRowCount = Math.ceil((columnHeight + gridProp.rowGap) / (gridProp.rowSize + gridProp.rowGap)); + + // Adding the grid classes. + columnEl.classList.add('g-col-lg-' + columnColCount, 'g-height-' + columnRowCount); + columnEl.classList.add('o_grid_item'); + + return {columnColCount: columnColCount, columnRowCount: columnRowCount}; +} /** * 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 diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index ce7a6dd14d0..37d21266941 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -939,40 +939,67 @@ var SnippetEditor = Widget.extend({ this.trigger_up('drag_and_drop_start'); this.options.wysiwyg.odooEditor.automaticStepUnactive(); var self = this; + const rowEl = this.$target[0].parentNode; + + // Allow the grid mode if the option is present in the right panel or + // if the grid mode is already activated. + let hasGridLayoutOption = false; + this.trigger_up('user_value_widget_request', { + name: 'grid_mode', + allowParentOption: true, + onSuccess: () => hasGridLayoutOption = true, + }); + const allowGridMode = hasGridLayoutOption || rowEl.classList.contains('o_grid_mode'); // Number of grid columns and rows in the grid item (BS column). let columnColCount; let columnRowCount; - const rowEl = this.$target[0].parentNode; + this.dragState = {}; if (rowEl.classList.contains('row') && this.options.isWebsite) { - // Toggle grid mode if it is not already on. - if (!rowEl.classList.contains('o_grid_mode')) { - const containerEl = rowEl.parentNode; - gridUtils._toggleGridMode(containerEl); + if (allowGridMode) { + // Toggle grid mode if it is not already on. + if (!rowEl.classList.contains('o_grid_mode')) { + const containerEl = rowEl.parentNode; + gridUtils._toggleGridMode(containerEl); + } + + // Computing the moving column width and height in terms of columns + // and rows. + const columnStart = self.$target[0].style.gridColumnStart; + const columnEnd = self.$target[0].style.gridColumnEnd; + const rowStart = self.$target[0].style.gridRowStart; + const rowEnd = self.$target[0].style.gridRowEnd; + + columnColCount = columnEnd - columnStart; + columnRowCount = rowEnd - rowStart; + this.dragState.columnColCount = columnColCount; + this.dragState.columnRowCount = columnRowCount; + + // Storing the current grid and grid area to use them for the + // history. + this.dragState.previousGrid = rowEl; + this.dragState.prevGridArea = self.$target[0].style.gridArea; + + // Reload the images. + gridUtils._reloadLazyImages(this.$target[0]); + } else { + // If the column comes from a snippet that doesn't toggle the + // grid mode on drag, store its width and height to use them + // when the column goes over a grid dropzone. + const isImageColumn = gridUtils._checkIfImageColumn(this.$target[0]); + if (isImageColumn) { + // Store the image width and height if the column only + // contains an image. + const imageEl = this.$target[0].querySelector('img'); + this.dragState.columnWidth = parseFloat(imageEl.scrollWidth); + this.dragState.columnHeight = parseFloat(imageEl.scrollHeight); + } else { + this.dragState.columnWidth = parseFloat(this.$target[0].scrollWidth); + this.dragState.columnHeight = parseFloat(this.$target[0].scrollHeight); + } } - - this.dragState = {}; - // Computing the moving column width and height in terms of columns - // and rows. - const columnStart = self.$target[0].style.gridColumnStart; - const columnEnd = self.$target[0].style.gridColumnEnd; - const rowStart = self.$target[0].style.gridRowStart; - const rowEnd = self.$target[0].style.gridRowEnd; - - columnColCount = columnEnd - columnStart; - columnRowCount = rowEnd - rowStart; - this.dragState.columnColCount = columnColCount; - this.dragState.columnRowCount = columnRowCount; - // Deactivate the snippet so the overlay doesn't show. this.trigger_up('deactivate_snippet', {$snippet: self.$target}); - // Storing the current grid and grid area to use them for the - // history. - this.dragState.previousGrid = rowEl; - this.dragState.prevGridArea = self.$target[0].style.gridArea; - - // Reload the images. - gridUtils._reloadLazyImages(this.$target[0]); } const isPopup = this.$target[0].closest('div.s_popup'); @@ -1011,7 +1038,7 @@ var SnippetEditor = Widget.extend({ // Remove the siblings that belong to a snippet in grid mode // and put the identified grid mode snippets in their own "selector". const selectorGrids = new Set(); - if (this.$target[0].classList.contains('o_grid_item')) { + if (rowEl.classList.contains('row')) { if ($selectorSiblings) { // Looping backwards because elements are removed, so the // indexes are not lost. @@ -1062,6 +1089,18 @@ var SnippetEditor = Widget.extend({ // dropzone. const rowEl = $dropzone[0].parentNode; + // If the column doesn't come from a grid mode snippet. + if (!self.$target[0].classList.contains('o_grid_item')) { + // Converting the column to grid. + const spans = gridUtils._convertColumnToGrid(rowEl, self.$target[0], self.dragState.columnWidth, self.dragState.columnHeight); + columnColCount = spans.columnColCount; + columnRowCount = spans.columnRowCount; + + // Storing the column spans. + self.dragState.columnColCount = columnColCount; + self.dragState.columnRowCount = columnRowCount; + } + // Creating the drag helper. const dragHelperEl = document.createElement('div'); dragHelperEl.classList.add('o_we_drag_helper'); @@ -1197,21 +1236,32 @@ var SnippetEditor = Widget.extend({ $el = $el.filter(this.$dropZones); if ($el.length) { // If the column is not dropped inside a dropzone. - if (this.$target[0].classList.contains('o_grid_item')) { - if ($el[0].classList.contains('oe_grid_zone')) { - // Case when a column is dropped near a grid. - // Placing it in the top left corner. - this.$target[0].style.gridArea = `1 / 1 / ${1 + this.dragState.columnRowCount} / ${1 + this.dragState.columnColCount}`; - const rowEl = $el[0].parentNode; - const rowCount = Math.max(rowEl.dataset.rowCount, 1 + this.dragState.columnRowCount); - rowEl.dataset.rowCount = rowCount; + if ($el[0].classList.contains('oe_grid_zone')) { + // Case when a column is dropped near a grid. + const rowEl = $el[0].parentNode; - // Setting the z-index to the maximum of the grid. - gridUtils._setElementToMaxZindex(this.$target[0], rowEl); - } else { - // Case when a column is dropped near a non-grid dropzone. + // If the column doesn't come from a snippet in grid mode, + // convert it. + if (!this.$target[0].classList.contains('o_grid_item')) { + const spans = gridUtils._convertColumnToGrid(rowEl, this.$target[0], this.dragState.columnWidth, this.dragState.columnHeight); + this.dragState.columnColCount = spans.columnColCount; + this.dragState.columnRowCount = spans.columnRowCount; + } + + // Placing it in the top left corner. + this.$target[0].style.gridArea = `1 / 1 / ${1 + this.dragState.columnRowCount} / ${1 + this.dragState.columnColCount}`; + const rowCount = Math.max(rowEl.dataset.rowCount, 1 + this.dragState.columnRowCount); + rowEl.dataset.rowCount = rowCount; + + // Setting the z-index to the maximum of the grid. + gridUtils._setElementToMaxZindex(this.$target[0], rowEl); + } else { + if (this.$target[0].classList.contains('o_grid_item')) { + // Case when a grid column is dropped near a non-grid + // dropzone. this.$target[0].classList.remove('o_grid_item'); this.$target[0].style.removeProperty('z-index'); + 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'); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 4a1d7e7d94b..987e74e59c3 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -4440,12 +4440,10 @@ registry.sizing = SnippetOptionWidget.extend({ handleEl.classList.toggle('readonly', isMobileView && isGridHandle); } - // Hiding the move handle for some snippets so we can't drag them - // and so we can't toggle the grid mode. + // Hiding the move handle in mobile view so we can't drag the + // columns. const moveHandleEl = this.$overlay[0].querySelector('.o_move_handle'); - const untoggleableColumns = '.s_masonry_block, .s_showcase, .s_features_grid, .s_website_form'; - const disableToggle = this.$target[0].closest(untoggleableColumns); - moveHandleEl.classList.toggle('d-none', disableToggle || isMobileView); + moveHandleEl.classList.toggle('d-none', isMobileView); // Hiding/showing the arrows. if (isGrid) { diff --git a/addons/web_editor/static/src/scss/web_editor.frontend.scss b/addons/web_editor/static/src/scss/web_editor.frontend.scss index 47a3d8ca914..22dc219d083 100644 --- a/addons/web_editor/static/src/scss/web_editor.frontend.scss +++ b/addons/web_editor/static/src/scss/web_editor.frontend.scss @@ -59,7 +59,7 @@ > * { width: 100%; - margin: 0; + margin: 0 !important; padding: var(--grid-item-padding-y) var(--grid-item-padding-x) !important; } } diff --git a/addons/website/static/src/snippets/s_product_list/000.scss b/addons/website/static/src/snippets/s_product_list/000.scss index 9910ce5baab..bf3a4ffab6c 100644 --- a/addons/website/static/src/snippets/s_product_list/000.scss +++ b/addons/website/static/src/snippets/s_product_list/000.scss @@ -2,11 +2,14 @@ .s_product_list { padding-top: 20px; + > div > .row:not(.o_grid_mode) > div { + height: 200px; + } + > div > .row > div { position: relative; margin-bottom: 20px; // without this style the columns go directly to the top of the bellow ones. - height: 200px; text-align: center; a { diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml index 769a6270ace..faf08a0d559 100644 --- a/addons/website/views/snippets/snippets.xml +++ b/addons/website/views/snippets/snippets.xml @@ -477,8 +477,8 @@