[IMP] website, *: introduce inputs in the left panel UI
* web_editor, website_sale Part of https://github.com/odoo/odoo/pull/38959 task-2066614
This commit is contained in:
@@ -353,6 +353,12 @@ var SnippetEditor = Widget.extend({
|
||||
el.parentNode.insertBefore(checkboxEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
uiEl.querySelectorAll('we-input').forEach(el => {
|
||||
const infos = this._extraInfoFromDescriptionElement(el);
|
||||
const inputEl = options.Class.prototype.buildInputElement(infos.title, infos.options);
|
||||
el.parentNode.insertBefore(inputEl, el);
|
||||
el.parentNode.removeChild(el);
|
||||
});
|
||||
|
||||
return $optionSection;
|
||||
},
|
||||
|
||||
@@ -10,6 +10,50 @@ var summernoteCustomColors = require('web_editor.rte.summernote_custom_colors');
|
||||
var qweb = core.qweb;
|
||||
var _t = core._t;
|
||||
|
||||
/**
|
||||
* window.getComputedStyle cannot work properly with CSS shortcuts (like
|
||||
* 'border-width' which is a shortcut for the top + right + bottom + left border
|
||||
* widths. If an options wants to customize such a shortcut, it should be listed
|
||||
* here with the non-shortcuts property it stands for, in order.
|
||||
*
|
||||
* @type {Object<string[]>}
|
||||
*/
|
||||
const CSS_SHORTHANDS = {
|
||||
'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
|
||||
'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
|
||||
};
|
||||
/**
|
||||
* Key-value mapping to list converters from an unit A to an unit B.
|
||||
* - The key is a string in the format '$1-$2' where $1 is the CSS symbol of
|
||||
* unit A and $2 is the CSS symbol of unit B.
|
||||
* - The value is a function that converts the received value (expressed in
|
||||
* unit A) to another value expressed in unit B. Two other parameters is
|
||||
* received: the css property on which the unit applies and the jQuery element
|
||||
* on which that css property may change.
|
||||
*/
|
||||
const CSS_UNITS_CONVERSION = {
|
||||
's-ms': () => 1000,
|
||||
'ms-s': () => 0.001,
|
||||
'rem-px': () => _computePxByRem(),
|
||||
'px-rem': () => _computePxByRem(true),
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the number of "px" needed to make a "rem" unit. Subsequent calls
|
||||
* returns the cached computed value.
|
||||
*
|
||||
* @param {boolean} [toRem=false]
|
||||
* @returns {float} - number of px by rem if 'toRem' is false
|
||||
* - the inverse otherwise
|
||||
*/
|
||||
function _computePxByRem(toRem) {
|
||||
if (_computePxByRem.PX_BY_REM === undefined) {
|
||||
const htmlStyle = window.getComputedStyle(document.documentElement);
|
||||
_computePxByRem.PX_BY_REM = parseFloat(htmlStyle['font-size']);
|
||||
}
|
||||
return toRem ? (1 / _computePxByRem.PX_BY_REM) : _computePxByRem.PX_BY_REM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a set of options for one snippet. The registry returned by this
|
||||
* module contains the names of the specialized SnippetOption which can be
|
||||
@@ -17,12 +61,14 @@ var _t = core._t;
|
||||
*/
|
||||
var SnippetOption = Widget.extend({
|
||||
events: {
|
||||
'mouseenter': '_onLinkEnter',
|
||||
'mouseenter we-button': '_onLinkEnter',
|
||||
'click': '_onLinkClick',
|
||||
'click we-button': '_onLinkClick',
|
||||
'mouseleave': '_onMouseleave',
|
||||
'mouseleave we-button': '_onMouseleave',
|
||||
'mouseenter we-button': '_onOptionPreview',
|
||||
'click we-button': '_onOptionSelection',
|
||||
'mouseleave we-button': '_onOptionCancel',
|
||||
|
||||
'input we-input input': '_onOptionPreview',
|
||||
'blur we-input input': '_onOptionInputBlur',
|
||||
'click we-input': '_onOptionInputClick',
|
||||
'keydown we-input input': '_onOptionInputKeydown',
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -119,6 +165,108 @@ var SnippetOption = Widget.extend({
|
||||
// Options
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Default option method which allows to handle an user input and set the
|
||||
* appropriate data attribute on the associated snippet.
|
||||
*
|
||||
* @param {boolean} previewMode - never 'reset' for this method (@see this.selectClass)
|
||||
* @param {Object} dataName - the data name to customize
|
||||
* @param {jQuery} $opt - the related DOMElement option
|
||||
*/
|
||||
setDataAttribute: function (previewMode, dataName, $opt) {
|
||||
let hasUserValue = false;
|
||||
const weInput = $opt[0];
|
||||
const inputValue = weInput.querySelector('input').value.trim();
|
||||
const inputUnit = weInput.dataset.unit;
|
||||
const saveUnit = weInput.dataset.saveUnit;
|
||||
const defaultValue = weInput.dataset.defaultValue;
|
||||
|
||||
let value;
|
||||
if (inputValue) {
|
||||
const numValue = parseFloat(inputValue);
|
||||
if (!isNaN(numValue)) {
|
||||
hasUserValue = true;
|
||||
value = this._convertNumericToUnit(numValue, inputUnit, saveUnit);
|
||||
}
|
||||
}
|
||||
if (value === undefined) {
|
||||
value = this._convertValueToUnit(defaultValue, saveUnit);
|
||||
}
|
||||
this.$target[0].dataset[dataName] = isNaN(value) ? defaultValue : parseFloat(value.toFixed(3));
|
||||
|
||||
var extraClass = weInput.dataset.extraClass;
|
||||
if (extraClass) {
|
||||
this.$target.toggleClass(extraClass, hasUserValue);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Default option method which allows to handle an user input and set the
|
||||
* appropriate css style on the associated snippet.
|
||||
*
|
||||
* @param {boolean} previewMode - never 'reset' for this method (@see this.selectClass)
|
||||
* @param {Object} mainCssProp - the cssProp to customize
|
||||
* @param {jQuery} $opt - the related DOMElement option
|
||||
*/
|
||||
setStyle: function (previewMode, mainCssProp, $opt) {
|
||||
let hasUserValue = false;
|
||||
const cssProps = CSS_SHORTHANDS[mainCssProp] || [mainCssProp];
|
||||
|
||||
// Join all inputs controlling the same css property and split user
|
||||
// input into sub-properties (note this code handles the two at the same
|
||||
// time but should normally not be combined).
|
||||
const $weInputs = this.$el.find(`[data-set-style=${mainCssProp}]`);
|
||||
const subValuesByInput = _.map($weInputs, weInput => {
|
||||
const inputValue = weInput.querySelector('input').value.trim();
|
||||
const inputUnit = weInput.dataset.unit;
|
||||
const saveUnit = weInput.dataset.saveUnit;
|
||||
const defaultValue = weInput.dataset.defaultValue;
|
||||
let convertedDefaultValue = this._convertValueToUnit(defaultValue, saveUnit, mainCssProp);
|
||||
convertedDefaultValue = isNaN(convertedDefaultValue) ? defaultValue : (parseFloat(convertedDefaultValue.toFixed(3)) + saveUnit);
|
||||
|
||||
const values = inputValue.split(/\s+/g).map(v => {
|
||||
const numValue = parseFloat(v);
|
||||
if (isNaN(numValue)) {
|
||||
return convertedDefaultValue;
|
||||
} else {
|
||||
hasUserValue = true;
|
||||
const value = this._convertNumericToUnit(numValue, inputUnit, saveUnit, mainCssProp);
|
||||
return parseFloat(value.toFixed(3)) + saveUnit;
|
||||
}
|
||||
});
|
||||
while (values.length < cssProps.length) {
|
||||
switch (values.length) {
|
||||
case 1:
|
||||
case 2:
|
||||
values.push(values[0]);
|
||||
break;
|
||||
case 3:
|
||||
values.push(values[1]);
|
||||
break;
|
||||
default:
|
||||
values.push(values[values.length - 1]);
|
||||
}
|
||||
}
|
||||
return values;
|
||||
});
|
||||
|
||||
for (const cssProp of cssProps) {
|
||||
// Always reset the inline style first to not put inline style on an
|
||||
// element which already have this style through css stylesheets.
|
||||
this.$target[0].style.setProperty(cssProp, '');
|
||||
}
|
||||
const styles = window.getComputedStyle(this.$target[0]);
|
||||
cssProps.forEach((cssProp, i) => {
|
||||
const cssValue = subValuesByInput.map(inputSubValues => inputSubValues[i]).join(' ');
|
||||
if (styles[cssProp] !== cssValue) {
|
||||
this.$target[0].style.setProperty(cssProp, cssValue, 'important');
|
||||
}
|
||||
});
|
||||
|
||||
var extraClass = $opt[0].dataset.extraClass;
|
||||
if (extraClass) {
|
||||
this.$target.toggleClass(extraClass, hasUserValue);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Default option method which allows to select one and only one class in
|
||||
* the option classes set and set it on the associated snippet. The common
|
||||
@@ -156,11 +304,11 @@ var SnippetOption = Widget.extend({
|
||||
* @see this.selectClass
|
||||
*/
|
||||
toggleClass: function (previewMode, value, $opt) {
|
||||
var $lis = this.$el.find('[data-toggle-class]');
|
||||
var classes = $lis.map(function () {
|
||||
const $opts = this.$el.find('[data-toggle-class]').not('[data-set-style]');
|
||||
const classes = $opts.map(function () {
|
||||
return $(this).data('toggleClass');
|
||||
}).get().join(' ');
|
||||
var activeClasses = $lis.filter('.active, :has(.active)').map(function () {
|
||||
const activeClasses = $opts.filter('.active, :has(.active)').map(function () {
|
||||
return $(this).data('toggleClass');
|
||||
}).get().join(' ');
|
||||
|
||||
@@ -214,6 +362,48 @@ var SnippetOption = Widget.extend({
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Converts the given (value + unit) string to a numeric value expressed in
|
||||
* the given css unit.
|
||||
*
|
||||
* e.g. fct('400ms', 's') -> 0.4
|
||||
*
|
||||
* @param {string} value
|
||||
* @param {string} unitTo
|
||||
* @param {string} [cssProp]
|
||||
* @returns {number}
|
||||
*/
|
||||
_convertValueToUnit: function (value, unitTo, cssProp) {
|
||||
const m = value.trim().match(/^([0-9.]+)(\w*)$/);
|
||||
if (!m) {
|
||||
return NaN;
|
||||
}
|
||||
const numValue = parseFloat(m[1]);
|
||||
const valueUnit = m[2];
|
||||
return this._convertNumericToUnit(numValue, valueUnit, unitTo, cssProp);
|
||||
},
|
||||
/**
|
||||
* Converts the given numeric value expressed in the given css unit into
|
||||
* the corresponding numeric value expressed in the other given css unit.
|
||||
*
|
||||
* e.g. fct(400, 'ms', 's') -> 0.4
|
||||
*
|
||||
* @param {number} value
|
||||
* @param {string} unitFrom
|
||||
* @param {string} unitTo
|
||||
* @param {string} [cssProp]
|
||||
* @returns {number}
|
||||
*/
|
||||
_convertNumericToUnit: function (value, unitFrom, unitTo, cssProp) {
|
||||
if (Math.abs(value) < Number.EPSILON || unitFrom === unitTo) {
|
||||
return value;
|
||||
}
|
||||
const converter = CSS_UNITS_CONVERSION[`${unitFrom}-${unitTo}`];
|
||||
if (converter === undefined) {
|
||||
throw new Error(`Cannot convert ${unitFrom} into ${unitTo} !`);
|
||||
}
|
||||
return value * converter(cssProp, this.$target);
|
||||
},
|
||||
/**
|
||||
* Reactivate the options that were activated before previews.
|
||||
*/
|
||||
@@ -266,13 +456,16 @@ var SnippetOption = Widget.extend({
|
||||
var $el = $(data[0]);
|
||||
var methods = data[1];
|
||||
|
||||
const isInput = $el.is('we-input');
|
||||
|
||||
Object.keys(methods).forEach(methodName => {
|
||||
if (this[methodName]) {
|
||||
if (previewMode === true) {
|
||||
this.__methodNames.push(methodName);
|
||||
}
|
||||
this[methodName](previewMode, methods[methodName], $el);
|
||||
if (!this[methodName]) {
|
||||
return;
|
||||
}
|
||||
if (previewMode === true && !isInput) {
|
||||
this.__methodNames.push(methodName);
|
||||
}
|
||||
this[methodName](previewMode, methods[methodName], $el);
|
||||
});
|
||||
});
|
||||
this.__methodNames = _.uniq(this.__methodNames);
|
||||
@@ -292,6 +485,9 @@ var SnippetOption = Widget.extend({
|
||||
*/
|
||||
_setActive: function () {
|
||||
var self = this;
|
||||
|
||||
// --- TOGGLE CLASS ---
|
||||
|
||||
this.$el.find('[data-toggle-class]')
|
||||
.removeClass('active')
|
||||
.filter(function () {
|
||||
@@ -300,6 +496,8 @@ var SnippetOption = Widget.extend({
|
||||
})
|
||||
.addClass('active');
|
||||
|
||||
// --- SELECT CLASS ---
|
||||
|
||||
// Get submenus which are not inside submenus
|
||||
var $submenus = this.$el.find('we-collapse-area, we-select')
|
||||
.not('we-collapse-area *, we-select *');
|
||||
@@ -330,6 +528,92 @@ var SnippetOption = Widget.extend({
|
||||
.last()
|
||||
.addClass('active');
|
||||
}
|
||||
|
||||
// --- SET DATA ATTRIBUTE --- (note: important to be done last because of active removal)
|
||||
|
||||
this.el.querySelectorAll('[data-set-data-attribute]').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
const inputEl = el.querySelector('input');
|
||||
if (inputEl === document.activeElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputUnit = el.dataset.unit;
|
||||
const saveUnit = el.dataset.saveUnit;
|
||||
const defaultValue = el.dataset.defaultValue;
|
||||
const convertedDefaultValue = this._convertValueToUnit(defaultValue, inputUnit);
|
||||
|
||||
let value = this.$target[0].dataset[el.dataset.setDataAttribute];
|
||||
let numValue = parseFloat(value);
|
||||
if (!isNaN(numValue)) {
|
||||
numValue = this._convertNumericToUnit(numValue, saveUnit, inputUnit);
|
||||
}
|
||||
if (isNaN(numValue) || Math.abs(numValue - convertedDefaultValue) < Number.EPSILON) {
|
||||
value = '';
|
||||
} else {
|
||||
value = parseFloat(numValue.toFixed(3));
|
||||
}
|
||||
inputEl.value = value;
|
||||
});
|
||||
|
||||
// --- SET STYLE --- (note: important to be done last because of active removal)
|
||||
|
||||
let styles;
|
||||
const seenSetStyles = [];
|
||||
this.el.querySelectorAll('[data-set-style]').forEach(el => {
|
||||
const mainCssProp = el.dataset.setStyle;
|
||||
if (seenSetStyles.includes(mainCssProp)) {
|
||||
return;
|
||||
}
|
||||
seenSetStyles.push(mainCssProp);
|
||||
|
||||
const $els = this.$el.find(`[data-set-style="${mainCssProp}"]`);
|
||||
$els.removeClass('active');
|
||||
const $inputs = $els.find('> input');
|
||||
if (_.any($inputs, input => input === document.activeElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
styles = styles || window.getComputedStyle(this.$target[0]);
|
||||
const cssProps = CSS_SHORTHANDS[mainCssProp] || [mainCssProp];
|
||||
|
||||
const valuesByProp = cssProps.map(cssProp => {
|
||||
const cssValue = styles[cssProp];
|
||||
if (!cssValue) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return cssValue.split(/\s+/).map((v, i) => {
|
||||
const inputUnit = $els[i].dataset.unit;
|
||||
const numValue = this._convertValueToUnit(v, inputUnit, mainCssProp);
|
||||
return isNaN(numValue) ? v : numValue;
|
||||
});
|
||||
});
|
||||
_.each($els, (el, i) => {
|
||||
let values = valuesByProp.map(propValues => propValues[i]);
|
||||
|
||||
if (values.length === 4 && Math.abs(values[3] - values[1]) < Number.EPSILON) {
|
||||
values.pop();
|
||||
}
|
||||
if (values.length === 3 && Math.abs(values[2] - values[0]) < Number.EPSILON) {
|
||||
values.pop();
|
||||
}
|
||||
if (values.length === 2 && Math.abs(values[1] - values[0]) < Number.EPSILON) {
|
||||
values.pop();
|
||||
}
|
||||
|
||||
const inputUnit = el.dataset.unit;
|
||||
const defaultValue = el.dataset.defaultValue;
|
||||
const convertedDefaultValue = this._convertValueToUnit(defaultValue, inputUnit, mainCssProp);
|
||||
if (values.length === 1 && Math.abs(values[0] - convertedDefaultValue) < Number.EPSILON) {
|
||||
values.pop();
|
||||
}
|
||||
|
||||
values = values.filter(v => isFinite(v));
|
||||
values = values.map(v => parseFloat(v.toFixed(3)));
|
||||
$inputs[i].value = (values.length ? values.join(' ') : '');
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -351,14 +635,71 @@ var SnippetOption = Widget.extend({
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Called when a option link is entered -> activates the related option in
|
||||
* preview mode.
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onOptionInputBlur: function (ev) {
|
||||
// Sometimes, an input is focusout for internal reason (like an undo
|
||||
// recording) then focused again manually in the same JS stack
|
||||
// execution. In that case, the blur should not trigger an option
|
||||
// selection as the user did not leave the input. We thus defer the blur
|
||||
// handling to then check that the target is indeed still blurred before
|
||||
// executing the actual option selection.
|
||||
setTimeout(() => {
|
||||
if (ev.currentTarget === document.activeElement) {
|
||||
return;
|
||||
}
|
||||
this._onOptionSelection(ev);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onOptionInputClick: function (ev) {
|
||||
const inputEl = ev.currentTarget.querySelector('input');
|
||||
if (inputEl) {
|
||||
inputEl.select();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onOptionInputKeydown: function (ev) {
|
||||
const input = ev.currentTarget;
|
||||
let value = parseFloat(input.value || input.placeholder);
|
||||
if (isNaN(value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let step = parseFloat(input.parentNode.dataset.step);
|
||||
if (isNaN(step)) {
|
||||
step = 1.0;
|
||||
}
|
||||
switch (ev.which) {
|
||||
case $.ui.keyCode.UP:
|
||||
value += step;
|
||||
break;
|
||||
case $.ui.keyCode.DOWN:
|
||||
value -= step;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
input.value = parseFloat(value.toFixed(3));
|
||||
$(input).trigger('input');
|
||||
},
|
||||
/**
|
||||
* Called when a option link is entered or an option input content is being
|
||||
* modified -> activates the related option in preview mode.
|
||||
*
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onLinkEnter: function (ev) {
|
||||
var $opt = $(ev.target).closest('we-button');
|
||||
_onOptionPreview: function (ev) {
|
||||
var $opt = $(ev.target).closest('we-button, we-input');
|
||||
if (!$opt.length) {
|
||||
return;
|
||||
}
|
||||
@@ -371,13 +712,14 @@ var SnippetOption = Widget.extend({
|
||||
this.$target.trigger('snippet-option-preview', [this]);
|
||||
},
|
||||
/**
|
||||
* Called when an option link is clicked -> activates the related option.
|
||||
* Called when an option link is clicked or an option input content is
|
||||
* validated -> activates the related option.
|
||||
*
|
||||
* @private
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onLinkClick: function (ev) {
|
||||
var $opt = $(ev.target).closest('we-button');
|
||||
_onOptionSelection: function (ev) {
|
||||
var $opt = $(ev.target).closest('we-button, we-input');
|
||||
if (ev.isDefaultPrevented() || !$opt.length || !$opt.is(':hasData')) {
|
||||
return;
|
||||
}
|
||||
@@ -393,7 +735,7 @@ var SnippetOption = Widget.extend({
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onMouseleave: function () {
|
||||
_onOptionCancel: function () {
|
||||
if (this.__click) {
|
||||
return;
|
||||
}
|
||||
@@ -472,6 +814,42 @@ var SnippetOption = Widget.extend({
|
||||
|
||||
return groupEl;
|
||||
},
|
||||
/**
|
||||
* Build the correct DOM for a we-input element.
|
||||
*
|
||||
* @static
|
||||
* @param {string} [title]
|
||||
* @param {Object} [options] - @see this.buildElement
|
||||
*/
|
||||
buildInputElement: function (title, options) {
|
||||
const inputWrapperEl = SnippetOption.prototype.buildElement('we-input', title, options);
|
||||
|
||||
var unit = inputWrapperEl.dataset.unit;
|
||||
if (unit === undefined) {
|
||||
unit = 'px';
|
||||
}
|
||||
inputWrapperEl.dataset.unit = unit;
|
||||
if (inputWrapperEl.dataset.saveUnit === undefined) {
|
||||
inputWrapperEl.dataset.saveUnit = unit;
|
||||
}
|
||||
|
||||
var defaultValue = inputWrapperEl.dataset.defaultValue;
|
||||
if (defaultValue === undefined) {
|
||||
defaultValue = ('0' + unit);
|
||||
}
|
||||
inputWrapperEl.dataset.defaultValue = defaultValue;
|
||||
|
||||
var inputEl = document.createElement('input');
|
||||
inputEl.setAttribute('type', 'text');
|
||||
inputEl.setAttribute('placeholder', defaultValue.replace(unit, ''));
|
||||
inputWrapperEl.appendChild(inputEl);
|
||||
|
||||
var unitEl = document.createElement('span');
|
||||
unitEl.textContent = unit;
|
||||
inputWrapperEl.appendChild(unitEl);
|
||||
|
||||
return inputWrapperEl;
|
||||
},
|
||||
/**
|
||||
* Build the correct DOM for a we-select element.
|
||||
*
|
||||
@@ -719,7 +1097,7 @@ registry.sizing = SnippetOption.extend({
|
||||
});
|
||||
_.each(this.$overlay.find(".o_handle.n, .o_handle.s"), function (handle) {
|
||||
var $handle = $(handle);
|
||||
var direction = $handle.hasClass('n') ? 'top': 'bottom';
|
||||
var direction = $handle.hasClass('n') ? 'top' : 'bottom';
|
||||
$handle.height(self.$target.css('padding-' + direction));
|
||||
});
|
||||
this.$target.trigger('content_changed');
|
||||
@@ -1546,5 +1924,7 @@ registry.many2one = SnippetOption.extend({
|
||||
return {
|
||||
Class: SnippetOption,
|
||||
registry: registry,
|
||||
CSS_SHORTHANDS: CSS_SHORTHANDS,
|
||||
CSS_UNITS_CONVERSION: CSS_UNITS_CONVERSION,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -369,6 +369,31 @@ body.editor_enable.editor_has_snippets {
|
||||
}
|
||||
}
|
||||
|
||||
we-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> we-title {
|
||||
min-width: 60%;
|
||||
margin-right: 5%;
|
||||
}
|
||||
> input {
|
||||
flex: 1 1 auto;
|
||||
width: 0;
|
||||
min-width: 3em;
|
||||
margin: 0 2px 0 0;
|
||||
padding: 0 2px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background-color: var(--o-we-bg-color-dark);
|
||||
color: var(--o-we-color);
|
||||
text-align: right;
|
||||
}
|
||||
> span {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
// FIXME temporary fix for m2o option for example
|
||||
position: static !important;
|
||||
|
||||
@@ -327,29 +327,11 @@ options.registry.carousel = options.Class.extend({
|
||||
this.$target.carousel('prev');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Changes the interval for autoplay.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
interval: function (previewMode, value) {
|
||||
this.$target.attr('data-interval', value);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this.$el.find('[data-interval]')
|
||||
.removeClass('active')
|
||||
.filter(`[data-interval=${this.$target.attr('data-interval')}]`)
|
||||
.addClass('active');
|
||||
},
|
||||
/**
|
||||
* Activates current slide for edition and vertical padding. Padding on the
|
||||
* carousel itself would mean the slides' backgrounds don't cover the padding.
|
||||
@@ -1864,7 +1846,7 @@ options.registry.SnippetMove = options.Class.extend({
|
||||
|
||||
// TODO this is kinda hack but not worth a complex system while no
|
||||
// other use case is implemented.
|
||||
$buttons.on('click', this._onLinkClick.bind(this));
|
||||
$buttons.on('click', this._onOptionSelection.bind(this));
|
||||
|
||||
return this._super(...arguments);
|
||||
},
|
||||
|
||||
@@ -1131,14 +1131,7 @@
|
||||
<we-divider/>
|
||||
<we-button data-select-class="s_carousel_default">Default</we-button>
|
||||
</we-select>
|
||||
<we-select string="Speed">
|
||||
<we-button data-interval="1000">1s</we-button>
|
||||
<we-button data-interval="2000">2s</we-button>
|
||||
<we-button data-interval="3000">3s</we-button>
|
||||
<we-button data-interval="5000">5s</we-button>
|
||||
<we-button data-interval="10000">10s</we-button>
|
||||
<we-button data-interval="0">Disable autoplay</we-button>
|
||||
</we-select>
|
||||
<we-input string="Speed" data-set-data-attribute="interval" data-unit="s" data-save-unit="ms" data-step="0.1"/>
|
||||
</div>
|
||||
|
||||
<div data-js="navTabs" data-selector="section .row > div" data-target=".nav-tabs">
|
||||
@@ -1303,8 +1296,8 @@
|
||||
|
||||
<div data-selector="section .row > div"
|
||||
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div">
|
||||
<we-checkbox string="Border" data-toggle-class="border"/>
|
||||
<we-checkbox string="Rounded" data-toggle-class="rounded"/>
|
||||
<we-input string="Border" data-set-style="border-width" data-extra-class="border"/>
|
||||
<we-input string="Rounded" data-set-style="border-radius" data-extra-class="rounded"/>
|
||||
<we-checkbox string="Shadow" data-toggle-class="shadow"/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.ppg = this.$target.closest('[data-ppg]').data('ppg');
|
||||
this.ppr = this.$target.closest('[data-ppr]').data('ppr');
|
||||
this.ppg = parseInt(this.$target.closest('[data-ppg]').data('ppg'));
|
||||
this.ppr = parseInt(this.$target.closest('[data-ppr]').data('ppr'));
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
@@ -113,29 +113,24 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
choosePpg: function (previewMode, value, $opt) {
|
||||
var self = this;
|
||||
new Dialog(this, {
|
||||
title: _t("Choose number of products"),
|
||||
$content: $(qweb.render('website_sale.dialog.choosePPG', {widget: this})),
|
||||
buttons: [
|
||||
{text: _t("Save"), classes: 'btn-primary', click: function () {
|
||||
var $input = this.$('input');
|
||||
var def = self._setPPG($input.val());
|
||||
if (!def) {
|
||||
$input.addClass('is-invalid');
|
||||
return;
|
||||
}
|
||||
return def.then(this.close.bind(this));
|
||||
}},
|
||||
{text: _t("Discard"), close: true},
|
||||
],
|
||||
}).open();
|
||||
setPpg: function (previewMode, value, $opt) {
|
||||
const ppg = parseInt($opt.find('input').val());
|
||||
if (!ppg || ppg < 1) {
|
||||
return false;
|
||||
}
|
||||
this.ppg = ppg;
|
||||
return this._rpc({
|
||||
route: '/shop/change_ppg',
|
||||
params: {
|
||||
'ppg': ppg,
|
||||
},
|
||||
}).then(() => reload());
|
||||
},
|
||||
/**
|
||||
* @see this.selectClass for params
|
||||
*/
|
||||
setPpr: function (previewMode, value, $opt) {
|
||||
this.ppr = parseInt(value);
|
||||
this._rpc({
|
||||
route: '/shop/change_ppr',
|
||||
params: {
|
||||
@@ -152,33 +147,17 @@ options.registry.WebsiteSaleGridLayout = options.Class.extend({
|
||||
* @override
|
||||
*/
|
||||
_setActive: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments);
|
||||
this._super(...arguments);
|
||||
|
||||
this.$el.find('[data-set-ppr]')
|
||||
.addBack('[data-set-ppr]')
|
||||
.removeClass('active')
|
||||
.filter(function () {
|
||||
var nbColumns = $(this).data('setPpr');
|
||||
return nbColumns === self.ppr;
|
||||
.filter((i, el) => {
|
||||
var nbColumns = parseInt($(el).data('setPpr'));
|
||||
return nbColumns === this.ppr;
|
||||
})
|
||||
.addClass('active');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {integer} ppg
|
||||
* @returns {Promise|false}
|
||||
*/
|
||||
_setPPG: function (ppg) {
|
||||
ppg = parseInt(ppg);
|
||||
if (!ppg || ppg < 1) {
|
||||
return false;
|
||||
}
|
||||
return this._rpc({
|
||||
route: '/shop/change_ppg',
|
||||
params: {
|
||||
'ppg': ppg,
|
||||
},
|
||||
}).then(reload);
|
||||
|
||||
this.$el.find('[data-set-ppg] > input').val(this.ppg);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<div t-name="website_sale.dialog.choosePPG">
|
||||
<div class="form-group row">
|
||||
<label for="ppg_input" class="col-lg-auto col-form-label">
|
||||
Choose the number of products to display
|
||||
</label>
|
||||
<div class="col">
|
||||
<input type="number" id="ppg_input" class="form-control" t-att-value="widget.ppg"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Products Search Bar settings dialog -->
|
||||
<div t-name="website_sale.dialog.productsSearchBar">
|
||||
<div class="form-group">
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<div data-js="WebsiteSaleGridLayout"
|
||||
data-selector="#products_grid .o_wsale_products_grid_table_wrapper > table"
|
||||
data-no-check="true">
|
||||
<we-button data-no-preview="true" data-choose-ppg="1">Choose number of products</we-button>
|
||||
<we-input string="Number of products" data-set-ppg="" data-unit="" data-no-preview="true"/>
|
||||
<we-select string="Number of Columns" class="o_wsale_ppr_submenu" data-no-preview="true">
|
||||
<we-button data-set-ppr="2">2</we-button>
|
||||
<we-button data-set-ppr="3">3</we-button>
|
||||
|
||||
Reference in New Issue
Block a user