[IMP] web_editor: rework background-sizing option
Previously, background size and position for snippets was configured in a modal, the user could choose a "focus point", which is rather unintuitive. They could also choose to use the "contain" background mode, with or without repeat, or a third option, where they could customize everything, from the background size in both directions to whether to repeat the background in only one or in both directions. This commit streamlines the background-sizing options, only the "cover" and "repeat pattern" options have been kept, as they cover almost every use case. Their configuration has been moved into the options menu for consistency with the rest of the editor, and the background's position is now chosen by clicking on the corresponding menu option, and dragging the background to the desired place. Part of https://github.com/odoo/odoo/pull/38959 maintask-2066614 task-2088304
This commit is contained in:
committed by
qsm-odoo
parent
01debf53f6
commit
dfd98f49e7
@@ -3,7 +3,6 @@ odoo.define('web_editor.snippets.options', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
|
||||
var Dialog = require('web.Dialog');
|
||||
var Widget = require('web.Widget');
|
||||
var weWidgets = require('wysiwyg.widgets');
|
||||
|
||||
@@ -1520,9 +1519,9 @@ registry.background = SnippetOption.extend({
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles the edition of snippet's background image position.
|
||||
* Handles the edition of snippets' background image position.
|
||||
*/
|
||||
registry['background_position'] = SnippetOption.extend({
|
||||
registry.BackgroundPosition = SnippetOption.extend({
|
||||
xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
|
||||
|
||||
/**
|
||||
@@ -1530,16 +1529,36 @@ registry['background_position'] = SnippetOption.extend({
|
||||
*/
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var self = this;
|
||||
this.$target.on('snippet-option-change', function () {
|
||||
self.onFocus();
|
||||
|
||||
this._initOverlay();
|
||||
this.img = document.createElement('img');
|
||||
this.img.src = this._getSrcFromCssValue();
|
||||
|
||||
this.$target.on('snippet-option-change', () => {
|
||||
// Hides option if the bg image is removed in favor of a bg color
|
||||
this._updateUI();
|
||||
// this.img is used to compute dragging speed
|
||||
this.img.src = this._getSrcFromCssValue();
|
||||
});
|
||||
|
||||
// Resize overlay content on window resize because background images
|
||||
// change size, and on carousel slide because they sometimes take up
|
||||
// more space and move elements around them.
|
||||
$(window).on('resize.bgposition', () => this._dimensionOverlay());
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy: function () {
|
||||
this._toggleBgOverlay(false);
|
||||
$(window).off('.bgposition');
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onFocus: function () {
|
||||
this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none');
|
||||
this._updateUI();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1547,77 +1566,31 @@ registry['background_position'] = SnippetOption.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens a Dialog to edit the snippet's backgroung image position.
|
||||
* Sets the background type (cover/repeat pattern).
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
backgroundPosition: function (previewMode, value, $opt) {
|
||||
var self = this;
|
||||
backgroundType: function (previewMode, value, $opt) {
|
||||
this.$target.toggleClass('o_bg_img_opt_repeat', value === 'repeat-pattern');
|
||||
this.$target.css('background-position', '');
|
||||
this.$target.css('background-size', '');
|
||||
},
|
||||
/**
|
||||
* Saves current background position and enables overlay.
|
||||
*
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
backgroundPositionOverlay: function (previewMode, value, $opt) {
|
||||
const position = this.$target.css('background-position').split(' ').map(v => parseInt(v));
|
||||
// Convert % values to pixels (because mouse movement is in pixels)
|
||||
const delta = this._getBackgroundDelta();
|
||||
this.originalPosition = {
|
||||
left: position[0] / 100 * delta.x || 0,
|
||||
top: position[1] / 100 * delta.y || 0,
|
||||
};
|
||||
this.currentPosition = _.clone(this.originalPosition);
|
||||
|
||||
this.previousState = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')];
|
||||
|
||||
this.bgPos = self.$target.css('background-position').split(' ');
|
||||
this.bgSize = self.$target.css('background-size').split(' ');
|
||||
|
||||
this.modal = new Dialog(null, {
|
||||
title: _t("Background Image Sizing"),
|
||||
$content: $(qweb.render('web_editor.dialog.background_position')),
|
||||
buttons: [
|
||||
{text: _t("Ok"), classes: 'btn-primary', close: true, click: _.bind(this._saveChanges, this)},
|
||||
{text: _t("Discard"), close: true, click: _.bind(this._discardChanges, this)},
|
||||
],
|
||||
}).open();
|
||||
|
||||
this.modal.opened().then(function () {
|
||||
// Fetch data form $target
|
||||
var value = ((self.$target.hasClass('o_bg_img_opt_contain')) ? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom')) ? 'custom' : 'cover'));
|
||||
self.modal.$('> label > input[value=' + value + ']').prop('checked', true);
|
||||
|
||||
if (self.$target.hasClass('o_bg_img_opt_repeat')) {
|
||||
self.modal.$('#o_bg_img_opt_contain_repeat').prop('checked', true);
|
||||
self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat');
|
||||
} else if (self.$target.hasClass('o_bg_img_opt_repeat_x')) {
|
||||
self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat_x');
|
||||
} else if (self.$target.hasClass('o_bg_img_opt_repeat_y')) {
|
||||
self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat_y');
|
||||
}
|
||||
|
||||
if (self.bgPos.length > 1) {
|
||||
self.bgPos = {
|
||||
x: self.bgPos[0],
|
||||
y: self.bgPos[1],
|
||||
};
|
||||
self.modal.$('#o_bg_img_opt_custom_pos_x').val(self.bgPos.x.replace('%', ''));
|
||||
self.modal.$('#o_bg_img_opt_custom_pos_y').val(self.bgPos.y.replace('%', ''));
|
||||
}
|
||||
if (self.bgSize.length > 1) {
|
||||
self.modal.$('#o_bg_img_opt_custom_size_x').val(self.bgSize[0].replace('%', ''));
|
||||
self.modal.$('#o_bg_img_opt_custom_size_y').val(self.bgSize[1].replace('%', ''));
|
||||
}
|
||||
|
||||
// Focus Point
|
||||
self.$focus = self.modal.$('.o_focus_point');
|
||||
self._updatePosInformation();
|
||||
|
||||
var imgURL = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image'));
|
||||
imgURL = (imgURL && imgURL[1]) || '';
|
||||
var $img = $('<img/>', {class: 'img img-fluid', src: imgURL});
|
||||
$img.on('load', function () {
|
||||
self._bindImageEvents($img);
|
||||
});
|
||||
$img.prependTo(self.modal.$('.o_bg_img_opt_object'));
|
||||
|
||||
// Bind events
|
||||
self.modal.$el.on('change', '> label > input', function (e) {
|
||||
self.modal.$('> .o_bg_img_opt').addClass('o_hidden')
|
||||
.filter('[data-value=' + e.target.value + ']')
|
||||
.removeClass('o_hidden');
|
||||
});
|
||||
self.modal.$el.on('change', 'input, select', function (e) {
|
||||
self._saveChanges();
|
||||
});
|
||||
self.modal.$('> label > input:checked').trigger('change');
|
||||
});
|
||||
this._toggleBgOverlay(true);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1625,116 +1598,219 @@ registry['background_position'] = SnippetOption.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Bind events on the given image so that the users can adapt the focus
|
||||
* point.
|
||||
* Initializes the overlay, binds events to the buttons, inserts it in
|
||||
* the DOM.
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $img
|
||||
*/
|
||||
_bindImageEvents: function ($img) {
|
||||
var self = this;
|
||||
_initOverlay: function () {
|
||||
this.$backgroundOverlay = $(qweb.render('web_editor.background_position_overlay'));
|
||||
this.$overlayContent = this.$backgroundOverlay.find('.o_we_overlay_content');
|
||||
this.$overlayBackground = this.$overlayContent.find('.o_overlay_background');
|
||||
|
||||
var mousedown = false;
|
||||
$img.on('mousedown', function (e) {
|
||||
mousedown = true;
|
||||
this.$backgroundOverlay.on('click', '.o_btn_apply', () => {
|
||||
this.$target.css('background-position', this.$bgDragger.css('background-position'));
|
||||
this._toggleBgOverlay(false);
|
||||
});
|
||||
$img.on('mousemove', function (e) {
|
||||
if (mousedown) {
|
||||
_update(e);
|
||||
this.$backgroundOverlay.on('click', '.o_btn_discard', () => {
|
||||
this._toggleBgOverlay(false);
|
||||
});
|
||||
|
||||
this.$backgroundOverlay.insertAfter(this.$overlay);
|
||||
},
|
||||
/**
|
||||
* Sets the overlay in the right place so that the draggable background
|
||||
* renders over the target, and size the background item like the target.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_dimensionOverlay: function () {
|
||||
if (!this.$backgroundOverlay.is('.oe_active')) {
|
||||
return;
|
||||
}
|
||||
// TODO: change #wrapwrap after web_editor rework.
|
||||
const $wrapwrap = $('#wrapwrap');
|
||||
const targetOffset = this.$target.offset();
|
||||
|
||||
this.$backgroundOverlay.css({
|
||||
width: $wrapwrap.innerWidth(),
|
||||
height: $wrapwrap.innerHeight(),
|
||||
});
|
||||
|
||||
this.$overlayContent.offset(targetOffset);
|
||||
|
||||
this.$bgDragger.css({
|
||||
width: `${this.$target.innerWidth()}px`,
|
||||
height: `${this.$target.innerHeight()}px`,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Toggles the overlay's display and renders a background clone inside of it.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} activate toggle the overlay on (true) or off (false)
|
||||
*/
|
||||
_toggleBgOverlay: function (activate) {
|
||||
if (this.$backgroundOverlay.is('.oe_active') === activate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!activate) {
|
||||
this.$backgroundOverlay.removeClass('oe_active');
|
||||
this.trigger_up('unblock_preview_overlays');
|
||||
this.trigger_up('activate_snippet', {$snippet: this.$target});
|
||||
|
||||
$(document).off('click.bgposition');
|
||||
return;
|
||||
}
|
||||
|
||||
this.trigger_up('hide_overlay');
|
||||
this.trigger_up('activate_snippet', {
|
||||
$snippet: this.$target,
|
||||
previewMode: true,
|
||||
});
|
||||
this.trigger_up('block_preview_overlays');
|
||||
|
||||
// Create empty clone of $target with same display size, make it draggable and give it a tooltip.
|
||||
this.$bgDragger = this.$target.clone().empty();
|
||||
this.$bgDragger.on('mousedown', this._onDragBackgroundStart.bind(this));
|
||||
this.$bgDragger.tooltip({
|
||||
title: 'Click and drag the background to adjust its position!',
|
||||
trigger: 'manual',
|
||||
container: this.$backgroundOverlay
|
||||
});
|
||||
|
||||
// Replace content of overlayBackground, activate the overlay and give it the right dimensions.
|
||||
this.$overlayBackground.empty().append(this.$bgDragger);
|
||||
this.$backgroundOverlay.addClass('oe_active');
|
||||
this._dimensionOverlay();
|
||||
this.$bgDragger.tooltip('show');
|
||||
|
||||
// Needs to be deferred or the click event that activated the overlay deactivates it as well.
|
||||
// This is caused by the click event which we are currently handling bubbling up to the document.
|
||||
window.setTimeout(() => $(document).on('click.bgposition', this._onDocumentClicked.bind(this)), 0);
|
||||
},
|
||||
/**
|
||||
* Disables background position if no background image, disables size inputs
|
||||
* in cover mode, and activates the proper select option.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this.$el.toggleClass('d-none', this.$target.css('background-image') === 'none');
|
||||
this.$el.find('we-input').toggleClass('d-none', this.$target.css('background-repeat') !== 'repeat');
|
||||
this.$el.find('[data-background-type]').removeClass('active')
|
||||
.filter(`[data-background-type=${this.$target.css('background-repeat') === 'repeat' ? 'repeat-pattern' : 'cover'}]`).addClass('active');
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* Returns the src value from a css value related to a background image
|
||||
* (e.g. "url('blabla')" => "blabla" / "none" => "").
|
||||
*
|
||||
* @private
|
||||
* @param {string} value
|
||||
* @returns {string}
|
||||
*/
|
||||
_getSrcFromCssValue: function (value) {
|
||||
if (value === undefined) {
|
||||
value = this.$target.css('background-image');
|
||||
}
|
||||
var srcValueWrapper = /url\(['"]*|['"]*\)|^none$/g;
|
||||
return value && value.replace(srcValueWrapper, '') || '';
|
||||
},
|
||||
/**
|
||||
* Returns the difference between the target's size and the background's
|
||||
* rendered size. Background position values in % are a percentage of this.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_getBackgroundDelta: function () {
|
||||
const bgSize = this.$target.css('background-size');
|
||||
if (bgSize !== 'cover') {
|
||||
let [width, height] = bgSize.split(' ');
|
||||
if (width === 'auto' && (height === 'auto' || !height)) {
|
||||
return {
|
||||
x: this.$target.outerWidth() - this.img.naturalWidth,
|
||||
y: this.$target.outerHeight() - this.img.naturalHeight,
|
||||
};
|
||||
}
|
||||
});
|
||||
$img.on('mouseup', function (e) {
|
||||
self.$focus.addClass('o_with_transition');
|
||||
_update(e);
|
||||
setTimeout(function () {
|
||||
self.$focus.removeClass('o_with_transition');
|
||||
}, 200);
|
||||
mousedown = false;
|
||||
});
|
||||
|
||||
function _update(e) {
|
||||
var posX = e.pageX - $(e.target).offset().left;
|
||||
var posY = e.pageY - $(e.target).offset().top;
|
||||
self.bgPos = {
|
||||
x: clipValue(posX / $img.width() * 100).toFixed(2) + '%',
|
||||
y: clipValue(posY / $img.height() * 100).toFixed(2) + '%',
|
||||
// At least one of width or height is not auto, so we can use it to calculate the other if it's not set
|
||||
[width, height] = [parseInt(width), parseInt(height)];
|
||||
return {
|
||||
x: this.$target.outerWidth() - (width || (height * this.img.naturalWidth / this.img.naturalHeight)),
|
||||
y: this.$target.outerHeight() - (height || (width * this.img.naturalHeight / this.img.naturalWidth)),
|
||||
};
|
||||
self._updatePosInformation();
|
||||
self._saveChanges();
|
||||
}
|
||||
|
||||
function clipValue(value) {
|
||||
return Math.max(0, Math.min(value, 100));
|
||||
}
|
||||
}
|
||||
const renderRatio = Math.max(
|
||||
this.$target.outerWidth() / this.img.naturalWidth,
|
||||
this.$target.outerHeight() / this.img.naturalHeight
|
||||
);
|
||||
|
||||
return {
|
||||
x: this.$target.outerWidth() - Math.round(renderRatio * this.img.naturalWidth),
|
||||
y: this.$target.outerHeight() - Math.round(renderRatio * this.img.naturalHeight),
|
||||
};
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Removes all option-related classes and style on the target element.
|
||||
* Drags the overlay's background image, copied to target on "Apply".
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_clean: function () {
|
||||
this.$target.removeClass('o_bg_img_opt_contain o_bg_img_opt_custom o_bg_img_opt_repeat o_bg_img_opt_repeat_x o_bg_img_opt_repeat_y')
|
||||
.css({
|
||||
'background-size': '',
|
||||
'background-position': '',
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Restores the target style before last edition made with the option.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_discardChanges: function () {
|
||||
this._clean();
|
||||
if (this.previousState) {
|
||||
this.$target.addClass(this.previousState[0]).css({
|
||||
'background-size': this.previousState[1],
|
||||
'background-position': this.previousState[2],
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Updates the visual representation of the chosen background position.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_updatePosInformation: function () {
|
||||
this.modal.$('.o_bg_img_opt_ui_info .o_x').text(this.bgPos.x);
|
||||
this.modal.$('.o_bg_img_opt_ui_info .o_y').text(this.bgPos.y);
|
||||
this.$focus.css({
|
||||
left: this.bgPos.x,
|
||||
top: this.bgPos.y,
|
||||
_onDragBackgroundStart: function (ev) {
|
||||
ev.preventDefault();
|
||||
this.$bgDragger.addClass('o_we_grabbing');
|
||||
const $document = $(this.ownerDocument);
|
||||
$document.on('mousemove.bgposition', this._onDragBackgroundMove.bind(this));
|
||||
$document.one('mouseup', () => {
|
||||
this.$bgDragger.removeClass('o_we_grabbing');
|
||||
$document.off('mousemove.bgposition');
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Updates the target element to match the chosen options.
|
||||
* Drags the overlay's background image, copied to target on "Apply".
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_saveChanges: function () {
|
||||
this._clean();
|
||||
_onDragBackgroundMove: function (ev) {
|
||||
ev.preventDefault();
|
||||
|
||||
var bgImgSize = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover';
|
||||
switch (bgImgSize) {
|
||||
case 'cover':
|
||||
this.$target.css('background-position', this.bgPos.x + ' ' + this.bgPos.y);
|
||||
break;
|
||||
case 'contain':
|
||||
this.$target.addClass('o_bg_img_opt_contain');
|
||||
this.$target.toggleClass('o_bg_img_opt_repeat', this.modal.$('#o_bg_img_opt_contain_repeat').prop('checked'));
|
||||
break;
|
||||
case 'custom':
|
||||
this.$target.addClass('o_bg_img_opt_custom');
|
||||
var sizeX = this.modal.$('#o_bg_img_opt_custom_size_x').val();
|
||||
var sizeY = this.modal.$('#o_bg_img_opt_custom_size_y').val();
|
||||
var posX = this.modal.$('#o_bg_img_opt_custom_pos_x').val();
|
||||
var posY = this.modal.$('#o_bg_img_opt_custom_pos_y').val();
|
||||
this.$target.addClass(this.modal.$('#o_bg_img_opt_custom_repeat').val())
|
||||
.css({
|
||||
'background-size': (sizeX ? sizeX + '%' : 'auto') + ' ' + (sizeY ? sizeY + '%' : 'auto'),
|
||||
'background-position': (posX ? posX + '%' : 'auto') + ' ' + (posY ? posY + '%' : 'auto'),
|
||||
});
|
||||
break;
|
||||
const delta = this._getBackgroundDelta();
|
||||
this.currentPosition.left = clamp(this.currentPosition.left + ev.originalEvent.movementX, [0, delta.x]);
|
||||
this.currentPosition.top = clamp(this.currentPosition.top + ev.originalEvent.movementY, [0, delta.y]);
|
||||
|
||||
const percentPosition = {
|
||||
left: this.currentPosition.left / delta.x * 100,
|
||||
top: this.currentPosition.top / delta.y * 100,
|
||||
};
|
||||
// In cover mode, one delta will be 0 and dividing by it will yield Infinity.
|
||||
// Defaulting to originalPosition in that case (can't be dragged)
|
||||
percentPosition.left = isFinite(percentPosition.left) ? percentPosition.left : this.originalPosition.left;
|
||||
percentPosition.top = isFinite(percentPosition.top) ? percentPosition.top : this.originalPosition.top;
|
||||
|
||||
this.$bgDragger.css('background-position', `${percentPosition.left}% ${percentPosition.top}%`);
|
||||
|
||||
function clamp(val, bounds) {
|
||||
// We sort the bounds because when one dimension of the rendered background is
|
||||
// larger than the container, delta is negative, and we want to use it as lower bound
|
||||
bounds = bounds.sort();
|
||||
return Math.max(bounds[0], Math.min(val, bounds[1]));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Deactivates the overlay if the user clicks outside of it.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onDocumentClicked: function (ev) {
|
||||
if (!ev.target.closest('.o_we_background_position_overlay')) {
|
||||
this._toggleBgOverlay(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -836,6 +836,29 @@ body.editor_enable.editor_has_snippets {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Background position overlay
|
||||
&.o_we_background_position_overlay {
|
||||
background-color: rgba(0,0,0,.7);
|
||||
z-index: auto;
|
||||
|
||||
.o_we_overlay_content {
|
||||
cursor: grab;
|
||||
|
||||
.o_we_grabbing {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.o_overlay_background > * {
|
||||
display: block !important;
|
||||
top: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
left: 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contact_menu {
|
||||
|
||||
@@ -13,102 +13,16 @@
|
||||
<!-- Snippet options -->
|
||||
<!--=================-->
|
||||
|
||||
<!-- Background position option dialog -->
|
||||
<div t-name="web_editor.dialog.background_position" class="o_bg_img_opt_modal">
|
||||
<label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="cover"/> Cover</label>
|
||||
<label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="contain"/> Contain</label>
|
||||
<label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="custom"/> Custom</label>
|
||||
|
||||
<div class="o_bg_img_opt" data-value="cover">
|
||||
<div class="o_bg_img_opt_cover_edition">
|
||||
<h6>Assign a focal point that will always be visible</h6>
|
||||
<div class="o_bg_img_opt_object">
|
||||
<span class="grid grid-1"/>
|
||||
<span class="grid grid-2"/>
|
||||
<span class="grid grid-3"/>
|
||||
<span class="grid grid-4"/>
|
||||
<span class="o_focus_point"/>
|
||||
<div class="o_bg_img_opt_ui_info">X: <span class="o_x"/> Y: <span class="o_y"/></div>
|
||||
<!-- Background position option overlay -->
|
||||
<t t-name="web_editor.background_position_overlay">
|
||||
<div class="o_we_background_position_overlay oe_overlay">
|
||||
<div class="o_we_overlay_content position-absolute">
|
||||
<div class="o_overlay_background"/>
|
||||
<div class="position-absolute d-flex m-1" style="top: 0">
|
||||
<button class="btn btn-primary mr-1 o_btn_apply">Apply</button>
|
||||
<button class="btn btn-danger o_btn_discard">Discard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_bg_img_opt" data-value="contain">
|
||||
<form>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-2 col-form-label" for="repeatInput">Repeat</label>
|
||||
<div class="col-md-5">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="o_bg_img_opt_contain_repeat"/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<form class="o_bg_img_opt" data-value="custom">
|
||||
<fieldset>
|
||||
<legend>Background size</legend>
|
||||
<p>Sets the width and height of the background image in percent of the parent element.</p>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="o_bg_img_opt_custom_size_x">Background width</label>
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<input type="number" min="0" max="100" class="form-control" id="o_bg_img_opt_custom_size_x" placeholder="auto"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="o_bg_img_opt_custom_size_y">Background height</label>
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<input type="number" min="0" max="100" class="form-control" id="o_bg_img_opt_custom_size_y" placeholder="auto"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Background position</legend>
|
||||
<p>Set the starting position of the background image.</p>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="o_bg_img_opt_custom_pos_x">Horizontal</label>
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<input type="number" min="0" max="100" class="form-control " id="o_bg_img_opt_custom_pos_x" placeholder="auto"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-md-3 col-form-label" for="o_bg_img_opt_custom_pos_y">Vertical</label>
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<input type="number" min="0" max="100" class="form-control" id="o_bg_img_opt_custom_pos_y" placeholder="auto"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Background repeat</legend>
|
||||
<p>Define if/how the background image will be repeated</p>
|
||||
<select class="form-control" id="o_bg_img_opt_custom_repeat">
|
||||
<option value="">No repeat</option>
|
||||
<option value="o_bg_img_opt_repeat">Repeat both</option>
|
||||
<option value="o_bg_img_opt_repeat_x">Repeat x</option>
|
||||
<option value="o_bg_img_opt_repeat_y">Repeat y</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -674,7 +674,7 @@ options.registry.parallax = options.Class.extend({
|
||||
*/
|
||||
onFocus: function () {
|
||||
this.trigger_up('option_update', {
|
||||
optionNames: ['background', 'background_position'],
|
||||
optionNames: ['background', 'BackgroundPosition'],
|
||||
name: 'target',
|
||||
data: this.$target.find('> .s_parallax_bg'),
|
||||
});
|
||||
|
||||
@@ -239,6 +239,14 @@ font[class*='bg-'] {
|
||||
.oe_img_bg {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.o_bg_img_opt_repeat {
|
||||
background-size: auto;
|
||||
background-repeat: repeat;
|
||||
}
|
||||
|
||||
// Compatibility <= 13.0, TODO remove?
|
||||
// -----------------------------------
|
||||
&.o_bg_img_opt_contain {
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
@@ -246,9 +254,6 @@ font[class*='bg-'] {
|
||||
&.o_bg_img_opt_custom {
|
||||
background-size: auto;
|
||||
}
|
||||
&.o_bg_img_opt_repeat {
|
||||
background-repeat: repeat;
|
||||
}
|
||||
&.o_bg_img_opt_repeat_x {
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
@@ -159,161 +159,3 @@ html[lang] > body.editor_enable [data-oe-translation-state] {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// MODALS
|
||||
body .modal {
|
||||
|
||||
// BACKGROUND IMAGE OPTIONS
|
||||
.o_bg_img_opt_modal .o_bg_img_opt {
|
||||
margin: 15px 0;
|
||||
|
||||
.help-control {
|
||||
@include o-position-absolute(15px, 15px);
|
||||
}
|
||||
|
||||
.help {
|
||||
margin: 15px 0;
|
||||
|
||||
.simulator {
|
||||
position: relative;
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
width: 120px;
|
||||
height: 100px;
|
||||
|
||||
.bg {
|
||||
@include o-position-absolute(0, 0, 0, 0);
|
||||
border: 1px dotted #888787;
|
||||
background-image: url(/web/image);
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.el {
|
||||
@include o-position-absolute(10px, 0, 10px, 0);
|
||||
border: 1px solid #222;
|
||||
}
|
||||
|
||||
&.contain_bg {
|
||||
.bg {
|
||||
@include o-position-absolute(0, 12px, 20px, 12px);
|
||||
}
|
||||
|
||||
.el {
|
||||
@include o-position-absolute(0, 0, 20px, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+* {
|
||||
clear: left;
|
||||
}
|
||||
}
|
||||
|
||||
.o_bg_img_opt_cover_edition {
|
||||
@include o-we-preview-box;
|
||||
padding: 0 25px 25px 25px;
|
||||
|
||||
h6 {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.o_bg_img_opt_object {
|
||||
position: relative;
|
||||
background: white;
|
||||
min-height: 10px;
|
||||
@include o-we-preview-content;
|
||||
|
||||
>img {
|
||||
cursor: crosshair;
|
||||
border-top: 1px solid #5A5A5A;
|
||||
border-bottom: 1px solid #111;
|
||||
}
|
||||
|
||||
&:hover .o_focus_point:before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.o_bg_img_opt_ui_info {
|
||||
animation: fadeInOut 2s ease forwards;
|
||||
@include o-position-absolute($bottom: 1px, $right: 0);
|
||||
display: block;
|
||||
padding: 3px 13px;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
color: #333;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
pointer-events: none;
|
||||
|
||||
span {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.o_x {
|
||||
margin-right: 10px
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
@include o-position-absolute($top: 33.33%, $left: 0);
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: fade-out(lighten($o-brand-primary, 30%), 0.5);
|
||||
pointer-events: none;
|
||||
|
||||
&.grid-2 {
|
||||
top: 66.66%
|
||||
}
|
||||
|
||||
&.grid-3 {
|
||||
top: 0;
|
||||
left: 33.33%;
|
||||
width: 1px;
|
||||
height: 100%
|
||||
}
|
||||
|
||||
&.grid-4 {
|
||||
top: 0;
|
||||
left: 66.66%;
|
||||
width: 1px;
|
||||
height: 100%
|
||||
}
|
||||
}
|
||||
|
||||
.o_focus_point {
|
||||
@include o-position-absolute($top: 0, $left: 0);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid white;
|
||||
border-radius: 100%;
|
||||
margin-top: -15px;
|
||||
margin-left: -15px;
|
||||
box-shadow: 0 0 1px #333;
|
||||
pointer-events: none;
|
||||
|
||||
&.o_with_transition {
|
||||
transition: all 0.2s ease 0s;
|
||||
}
|
||||
|
||||
&:before {
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-top: -37px;
|
||||
margin-left: -37px;
|
||||
border: 1px solid #EAEAEA;
|
||||
background: rgba(247, 76, 76, 0);
|
||||
border-radius: 100em;
|
||||
opacity: 1;
|
||||
box-shadow: 0 0 0 100em rgba(0, 0, 0, 0.33);
|
||||
transition: opacity 0.2s ease 0s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1226,11 +1226,20 @@
|
||||
data-selector="section, .parallax, .carousel-item"
|
||||
data-exclude=".s_hr, .s_image_gallery, .o_gallery .carousel-item"/>
|
||||
|
||||
<!-- Background Image -->
|
||||
<div data-js="background_position"
|
||||
<!-- Background Image Size/Position -->
|
||||
<div data-js="BackgroundPosition"
|
||||
data-selector="section, .parallax, .carousel-item"
|
||||
data-exclude=".o_gallery .carousel-item">
|
||||
<we-button data-background-position="true" data-no-preview="true">Background Image Sizing</we-button>
|
||||
<we-row>
|
||||
<we-select data-no-preview="true">
|
||||
<we-button data-background-type="cover">Cover</we-button>
|
||||
<we-button data-background-type="repeat-pattern">Repeat pattern</we-button>
|
||||
</we-select>
|
||||
<we-button class="fa fa-fw fa-arrows" title="Background Position"
|
||||
data-background-position-overlay="true" data-no-preview="true"/>
|
||||
</we-row>
|
||||
<we-input string="Background width" data-set-style="background-size" data-default-value="auto"/>
|
||||
<we-input string="Background height" data-set-style="background-size" data-default-value="auto"/>
|
||||
</div>
|
||||
|
||||
<!-- Parallax -->
|
||||
|
||||
Reference in New Issue
Block a user