[IMP] web_editor: add new way to toggle left panel widget visibility

Now, instead of extending updateUI, developers can extend
_computeVisibility and _computeWidgetVisibility to indicate if the
whole option or a specific widget needs to be visible or not.

Without any JS, developers can also add a 'data-dependencies' attribute
on widgets whose value is a comma-separated list of widget names. Widget
names are specificied through 'data-name'.

Other notable changes:

1) *All* the options UI of a same section are updated on a change (not
   only the widgets of the same option).

2) Back as it was before: the UI is only updated in non-preview mode
   (prevent flickering of background removal during color preview
   because of point 1).

Part of https://github.com/odoo/odoo/pull/41600

closes odoo/odoo#41600

Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
qsm-odoo
2019-12-16 09:14:01 +00:00
parent 684a19f0c1
commit 2cc8316c66
4 changed files with 337 additions and 216 deletions
@@ -25,6 +25,8 @@ var SnippetEditor = Widget.extend({
xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'],
custom_events: {
'option_update': '_onOptionUpdate',
'user_value_widget_request': '_onUserValueWidgetRequest',
'snippet_option_update': '_onSnippetOptionUpdate',
},
/**
@@ -676,6 +678,44 @@ var SnippetEditor = Widget.extend({
this.trigger_up('request_history_undo_record', {$target: this.$target});
this.removeSnippet();
},
/**
* @private
* @param {OdooEvent}
*/
_onSnippetOptionUpdate: async function (ev) {
if (ev.data.previewMode) {
ev.data.onSuccess();
return;
}
const proms1 = Object.keys(this.styles).map(key => {
return this.styles[key].updateUI({
forced: ev.data.widget,
noVisibility: true,
});
});
await Promise.all(proms1);
const proms2 = Object.keys(this.styles).map(key => {
return this.styles[key].updateUIVisibility();
});
await Promise.all(proms2);
ev.data.onSuccess();
},
/**
* @private
* @param {Event} ev
*/
_onUserValueWidgetRequest: function (ev) {
for (const key of Object.keys(this.styles)) {
const widget = this.styles[key].findWidget(ev.data.name);
if (widget) {
ev.data.onSuccess(widget);
return;
}
}
},
});
/**
@@ -148,6 +148,22 @@ const UserValueWidget = Widget.extend({
close: function () {
this._userValueWidgets.forEach(widget => widget.close());
},
/**
* @param {string} name
* @returns {UserValueWidget|null}
*/
findWidget: function (name) {
for (const widget of this._userValueWidgets) {
if (widget.getName() === name) {
return widget;
}
const depWidget = widget.findWidget(name);
if (depWidget) {
return depWidget;
}
}
return null;
},
/**
* Returns the value that the widget would hold if it was active, by default
* the internal value it holds.
@@ -169,6 +185,12 @@ const UserValueWidget = Widget.extend({
const possibleValues = this._methodsParams.optionsPossibleValues[methodName];
return possibleValues && possibleValues[0] || '';
},
/**
* @returns {string[]}
*/
getDependencies: function () {
return this._dependencies;
},
/**
* Returns the names of the option methods associated to the widget. Those
* are loaded with @see loadMethodsData.
@@ -194,6 +216,12 @@ const UserValueWidget = Widget.extend({
}
return params;
},
/**
* @returns {string} empty string if no name is used by the widget
*/
getName: function () {
return this._methodsParams.name || '';
},
/**
* Returns the user value that the widget currently holds. The value is a
* string, this is the value that will be received in the option methods
@@ -254,11 +282,14 @@ const UserValueWidget = Widget.extend({
this._methodsNames = [];
this._methodsParams = _.extend({}, extraParams);
this._methodsParams.optionsPossibleValues = {};
this._dependencies = [];
for (const key in this.el.dataset) {
const dataValue = this.el.dataset[key].trim();
if (validMethodNames.includes(key)) {
if (key === 'dependencies') {
this._dependencies.push(...dataValue.split(/\s*,\s*/g));
} else if (validMethodNames.includes(key)) {
this._methodsNames.push(key);
this._methodsParams.optionsPossibleValues[key] = dataValue.split(/\s*\|\s*/g);
} else {
@@ -313,6 +344,12 @@ const UserValueWidget = Widget.extend({
setValue: function (value, methodName) {
this._value = value;
},
/**
* @param {boolean} show
*/
toggleVisibility: function (show) {
this.el.classList.toggle('d-none', !show);
},
/**
* Updates the UI to match the user value the widget currently holds, only
* if the UI can currently be updated.
@@ -1427,6 +1464,22 @@ const SnippetOptionWidget = Widget.extend({
closeWidgets: function () {
this._userValueWidgets.forEach(widget => widget.close());
},
/**
* @param {string} name
* @returns {UserValueWidget|null}
*/
findWidget: function (name) {
for (const widget of this._userValueWidgets) {
if (widget.getName() === name) {
return widget;
}
const depWidget = widget.findWidget(name);
if (depWidget) {
return depWidget;
}
}
return null;
},
/**
* Sometimes, options may need to notify other options, even in parent
* editors. This can be done thanks to the 'option_update' event, which
@@ -1453,16 +1506,18 @@ const SnippetOptionWidget = Widget.extend({
setTarget: async function ($target) {
this.$target = $target;
await this.updateUI();
this.$target.trigger('snippet-option-change', [this]);
},
/**
* Updates the UI. For widget update, @see _computeWidgetState.
*
* @param {UserValueWidget} [forced=null]
* Only non-previewed widgets are updated, except for the one given here
* @param {boolean} [noVisibility=false]
* If true, only update widget values and their UI, not their visibility
* -> @see updateUIVisibility for toggling visibility only
* @returns {Promise}
*/
updateUI: async function (forced) {
updateUI: async function ({forced, noVisibility} = {}) {
// For each widget, for each of their option method, notify to the
// widget the current value they should hold according to the $target's
// current state, related for that method.
@@ -1494,6 +1549,49 @@ const SnippetOptionWidget = Widget.extend({
// hold have been updated).
return widget.updateUI(widget === forced);
});
if (!noVisibility) {
await this.updateUIVisibility();
}
return Promise.all(proms);
},
/**
* Updates the UI visibility - @see _computeVisibility. For widget update,
* @see _computeWidgetVisibility.
*
* @returns {Promise}
*/
updateUIVisibility: async function () {
const proms = this._userValueWidgets.map(async widget => {
const params = widget.getMethodsParams();
const show = await this._computeWidgetVisibility(widget.getName(), params);
if (!show) {
widget.toggleVisibility(false);
return;
}
const dependencies = widget.getDependencies();
const dependenciesOK = !dependencies.length || dependencies.some(depName => {
const toBeActive = (depName[0] !== '!');
if (!toBeActive) {
depName = depName.substr(1);
}
let widget = null;
this.trigger_up('user_value_widget_request', {
name: depName,
onSuccess: _widget => widget = _widget,
});
return widget && (widget.isActive() === toBeActive);
});
widget.toggleVisibility(dependenciesOK);
});
const showUI = await this._computeVisibility();
this.el.classList.toggle('d-none', !showUI);
return Promise.all(proms);
},
@@ -1501,6 +1599,13 @@ const SnippetOptionWidget = Widget.extend({
// Private
//--------------------------------------------------------------------------
/**
* @private
* @returns {Promise<boolean>|boolean}
*/
_computeVisibility: async function () {
return true;
},
/**
* Returns the string value that should be hold by the widget which is
* related to the given method name.
@@ -1565,6 +1670,15 @@ const SnippetOptionWidget = Widget.extend({
}
}
},
/**
* @private
* @param {string} widgetName
* @param {Object} params
* @returns {Promise<boolean>|boolean}
*/
_computeWidgetVisibility: async function (widgetName, params) {
return true;
},
/**
* @private
* @param {HTMLElement} el
@@ -1598,25 +1712,25 @@ const SnippetOptionWidget = Widget.extend({
return;
}
// If it is not preview mode, the user selected the option for good
// (so record the action)
if (!previewMode) {
this.trigger_up('request_history_undo_record', {$target: this.$target});
}
// Call widget option methods and update $target
await this._select(previewMode, widget);
// Trigger the related events notifying snippet changes
let evName = 'snippet-option-reset';
if (previewMode === false) {
evName = 'snippet-option-change';
} else if (previewMode === true) {
evName = 'snippet-option-preview';
}
this.$target.trigger(evName, [this]);
this.$target.trigger('content_changed');
// Update the UI of the correct widgets
if (!previewMode) {
await this.updateUI(widget);
} else {
await this.updateUI();
}
await new Promise(resolve => {
// Will update the UI of the correct widgets for all options
// related to the same $target/editor if necessary
this.trigger_up('snippet_option_update', {
widget: widget,
previewMode: previewMode,
onSuccess: () => resolve(),
});
});
}});
},
/**
@@ -1754,12 +1868,6 @@ const SnippetOptionWidget = Widget.extend({
* @returns {Promise}
*/
_select: async function (previewMode, widget) {
// If it is not preview mode, the user selected the option for good
// (so record the action)
if (!previewMode) {
this.trigger_up('request_history_undo_record', {$target: this.$target});
}
// Call each option method sequentially
for (const methodName of widget.getMethodsNames()) {
const widgetValue = widget.getValue(methodName);
@@ -2099,7 +2207,7 @@ registry.background = SnippetOptionWidget.extend({
if (previewMode === 'reset') {
// No background has been selected and we want to reset back to the
// original custom image
await this._setCustomBackground(this.__customImageSrc);
await this._setCustomBackground(this.__customImageSrc, previewMode);
return;
}
@@ -2120,16 +2228,21 @@ registry.background = SnippetOptionWidget.extend({
var options = this._getMediaDialogOptions();
var media = this._getEditableMedia();
var _editor = new weWidgets.MediaDialog(this, options, media).open();
_editor.on('save', this, data => {
this._onSaveMediaDialog(data).then(() => {
this.$target.trigger('content_changed');
return new Promise(resolve => {
const _editor = new weWidgets.MediaDialog(this, options, media).open();
let _saving = false;
_editor.on('save', this, data => {
_saving = true;
this._onSaveMediaDialog(data).then(() => resolve());
});
_editor.on('closed', this, () => {
if (media.classList.contains('o_we_fake_image')) {
media.parentNode.removeChild(media);
}
if (!_saving) {
resolve();
}
});
});
_editor.on('closed', this, () => {
if (media.classList.contains('o_we_fake_image')) {
media.parentNode.removeChild(media);
}
});
},
@@ -2163,7 +2276,6 @@ registry.background = SnippetOptionWidget.extend({
updateUI: async function () {
await this._super(...arguments);
var src = this._getSrcFromCssValue();
this.removeBgWidget.el.classList.toggle('d-none', !src);
if (src) {
var split = src.split('/');
this.editBgTextEl.textContent = split[split.length - 1];
@@ -2171,6 +2283,14 @@ registry.background = SnippetOptionWidget.extend({
this.editBgTextEl.textContent = this._getDefaultTextContent();
}
},
/**
* @override
*/
updateUIVisibility: async function () {
await this._super(...arguments);
var src = this._getSrcFromCssValue();
this.removeBgWidget.el.classList.toggle('d-none', !src);
},
//--------------------------------------------------------------------------
// Private
@@ -2267,12 +2387,18 @@ registry.background = SnippetOptionWidget.extend({
* @param {string} value
* @returns {Promise}
*/
_setCustomBackground: async function (value) {
_setCustomBackground: async function (value, previewMode) {
this.__customImageSrc = value;
this.background(false, this.__customImageSrc, {});
this.$target.toggleClass('oe_custom_bg', !!value);
await this.updateUI();
this.$target.trigger('snippet-option-change', [this]);
await new Promise(resolve => {
// Will update the UI of the correct widgets for all options
// related to the same $target/editor
this.trigger_up('snippet_option_update', {
previewMode: previewMode,
onSuccess: () => resolve(),
});
});
},
//--------------------------------------------------------------------------
@@ -2326,15 +2452,6 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
this._super.apply(this, arguments);
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
@@ -2369,7 +2486,14 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
*
* @see this.selectClass for params
*/
backgroundPositionOverlay: function (previewMode, widgetValue, params) {
backgroundPositionOverlay: async function (previewMode, widgetValue, params) {
// Updates the internal image
await new Promise(resolve => {
this.img = document.createElement('img');
this.img.addEventListener('load', () => resolve());
this.img.src = this._getSrcFromCssValue();
});
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();
@@ -2394,25 +2518,16 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* Disables background position if no background image, disables size inputs
* in cover mode.
*
* @override
*/
updateUI: async function () {
await this._super(...arguments);
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');
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeVisibility: function () {
return this._super(...arguments) && (this.$target.css('background-image') !== 'none');
},
/**
* @override
*/
@@ -503,21 +503,18 @@ options.registry.navTabs = options.Class.extend({
},
//--------------------------------------------------------------------------
// Public
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
updateUI: async function () {
await this._super(...arguments);
this.$el.filter('[data-remove-tab]').toggleClass('d-none', this.$tabPanes.length <= 2);
_computeWidgetVisibility: async function (widgetName, params) {
if (widgetName === 'remove_tab_opt') {
return (this.$tabPanes.length > 2);
}
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
@@ -1359,21 +1356,6 @@ options.registry.gallery = options.Class.extend({
this.mode('reset', this.getMode());
}
},
/**
* @override
*/
updateUI: async function () {
await this._super(...arguments);
this.$el.find('[data-interval]').closest('we-select')[0]
.classList.toggle('d-none', this.activeMode !== 'slideshow');
this.$el.find('[data-columns]').closest('we-select')[0]
.classList.toggle('d-none', !(this.activeMode === 'grid' || this.activeMode === 'masonry'));
this.el.querySelector('.o_w_image_spacing_option')
.classList.toggle('d-none', this.activeMode === 'slideshow');
},
//--------------------------------------------------------------------------
// Private
@@ -1527,28 +1509,16 @@ options.registry.countdown = options.Class.extend({
/**
* @override
*/
updateUI: async function () {
updateUIVisibility: async function () {
await this._super(...arguments);
const dataset = this.$target[0].dataset;
// End Action UI
this.$el.find('[data-attribute-name="redirectUrl"]')
.toggleClass('d-none', dataset.endAction !== 'redirect');
this.$el.find('.toggle-edit-message')
.toggleClass('d-none', dataset.endAction !== 'message');
this.$el.find('[data-toggle-class="hide-countdown"]')
.toggleClass('d-none', dataset.endAction === 'nothing');
// End Message UI
this.updateUIEndMessage();
// Styling UI
this.$el.find('[data-attribute-name="layoutBackground"], [data-attribute-name="progressBarStyle"]')
.toggleClass('d-none', dataset.layout === 'clean' || dataset.layout === 'text');
this.$el.find('[data-attribute-name="layoutBackgroundColor"]')
.toggleClass('d-none', dataset.layoutBackground === 'none');
this.$el.find('[data-attribute-name="progressBarWeight"], [data-attribute-name="progressBarColor"]')
.toggleClass('d-none', dataset.progressBarStyle === 'none');
},
/**
* @see this.updateUI
@@ -1677,35 +1647,24 @@ options.registry.topMenuTransparency = options.Class.extend({
});
options.registry.topMenuColor = options.Class.extend({
/**
* @override
*/
start: function () {
var self = this;
var def = this._super.apply(this, arguments);
this.$target.on('snippet-option-change', function () {
self.onFocus();
});
return def;
},
//--------------------------------------------------------------------------
// Public
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
updateUI: async function () {
await this._super(...arguments);
await new Promise(resolve => {
_computeVisibility: async function () {
const show = await this._super(...arguments);
if (!show) {
return false;
}
return new Promise(resolve => {
this.trigger_up('action_demand', {
actionName: 'get_page_option',
params: ['header_overlay'],
onSuccess: value => {
this.$el.toggleClass('d-none', !value);
resolve();
},
onSuccess: value => resolve(!!value),
});
});
},
@@ -1959,29 +1918,10 @@ options.registry.CoverProperties = options.Class.extend({
updateUI: async function () {
await this._super(...arguments);
// Only show options which are useful to the current cover
_.each(this.$el.children(), el => {
var $el = $(el);
if (!$el.is('[data-change]')) {
$el.removeClass('d-none');
['size', 'filters', 'text_size', 'text_align'].forEach(optName => {
var $opts = $el.find('[data-cover-opt="' + optName + '"]');
var notAllowed = (this.$target.data('use_' + optName) !== 'True');
if ($opts.length && (!this.$target.hasClass('o_record_has_cover') || notAllowed)) {
$el.addClass('d-none');
}
});
}
});
this.$el.find('[data-clear]').toggleClass('d-none', !this.$target.hasClass('o_record_has_cover'));
// Update saving dataset
this.$target[0].dataset.coverClass = this.$el.find('.active[data-cover-opt="size"]').data('selectClass') || '';
this.$target[0].dataset.textSizeClass = this.$el.find('.active[data-cover-opt="text_size"]').data('selectClass') || '';
this.$target[0].dataset.textAlignClass = this.$el.find('.active[data-cover-opt="text_align"]').data('selectClass') || '';
this.$target[0].dataset.coverClass = this.$el.find('[data-cover-opt-name="size"] .active').data('selectClass') || '';
this.$target[0].dataset.textSizeClass = this.$el.find('[data-cover-opt-name="text_size"] .active').data('selectClass') || '';
this.$target[0].dataset.textAlignClass = this.$el.find('[data-cover-opt-name="text_align"] .active').data('selectClass') || '';
this.$target[0].dataset.filterValue = this.$filterValueOpts.filter('.active').data('filterValue') || 0.0;
this.$target[0].dataset.filterColor = this.$filterColorOpts.filter('.active').data('filterColor') || '';
},
@@ -2010,6 +1950,19 @@ options.registry.CoverProperties = options.Class.extend({
}
return this._super(...arguments);
},
/**
* @override
*/
_computeWidgetVisibility: function (widgetName, params) {
const hasCover = this.$target.hasClass('o_record_has_cover');
if (widgetName === 'remove_cover_opt') {
return hasCover;
} else if (params.coverOptName) {
var notAllowed = (this.$target.data(`use_${params.coverOptName}`) !== 'True');
return (hasCover && !notAllowed);
}
return this._super(...arguments);
},
});
/**
@@ -2096,9 +2049,8 @@ options.registry.InnerChart = options.Class.extend({
* @override
*/
start: function () {
// Get the 2 color pickers to hide in updateUI
this.backSelectEl = this.el.querySelector('we-select[data-attribute-name="backgroundColor"]');
this.borderSelectEl = this.el.querySelector('we-select[data-attribute-name="borderColor"]');
this.backSelectEl = this.el.querySelector('[data-name="chart_bg_color_opt"]');
this.borderSelectEl = this.el.querySelector('[data-name="chart_border_color_opt"]');
// Build matrix content
this.tableEl = this.el.querySelector('we-matrix table');
@@ -2130,16 +2082,15 @@ options.registry.InnerChart = options.Class.extend({
if (!this.lastEditableSelectedInput.closest('table') || this.colorPaletteSelectedInput && !this.colorPaletteSelectedInput.closest('table')) {
this._setDefaultSelectedInput();
}
await this._super(...arguments);
// prevent the columns from becoming too small.
this.tableEl.classList.toggle('o_we_matrix_five_col', this.tableEl.querySelectorAll('tr:first-child th').length > 5);
this.el.querySelector('[data-attribute-name="stacked"]').classList.toggle('d-none', !this._isStackableChart() || this._getColumnCount() === 1);
// Disable color on inputs that don't use them
const notDisplayed = !this.colorPaletteSelectedInput;
this.backSelectEl.classList.toggle('d-none', notDisplayed);
this.borderSelectEl.classList.toggle('d-none', notDisplayed);
this.backSelectEl.querySelector('we-title').textContent = this._isPieChart() ? _t("Data Background Color") : _t("Dataset Background Color");
this.borderSelectEl.querySelector('we-title').textContent = this._isPieChart() ? _t("Data Border Color") : _t("Dataset Border Color");
// Dataset/Cell color
this.tableEl.querySelectorAll('input').forEach(el => el.style.border = '');
const selector = this._isPieChart() ? 'td input' : 'tr:first-child input';
@@ -2198,6 +2149,21 @@ options.registry.InnerChart = options.Class.extend({
}
return this._super(...arguments);
},
/**
* @override
*/
_computeWidgetVisibility: function (widgetName, params) {
switch (widgetName) {
case 'stacked_chart_opt': {
return this._getColumnCount() > 1;
}
case 'chart_bg_color_opt':
case 'chart_border_color_opt': {
return !!this.colorPaletteSelectedInput;
}
}
return this._super(...arguments);
},
/**
* Sets and reloads the data on the canvas if it has changed.
* Used in matrix related method.
@@ -2365,13 +2331,6 @@ options.registry.InnerChart = options.Class.extend({
_isPieChart: function () {
return ['pie', 'doughnut'].includes(this.$target[0].dataset.type);
},
/**
* @private
* @return {boolean}
*/
_isStackableChart: function () {
return ['bar', 'horizontalBar'].includes(this.$target[0].dataset.type);
},
/**
* Return the number of column minus header and button
* @private
+67 -60
View File
@@ -1170,7 +1170,7 @@
<div data-js="navTabs" data-selector="section .row > div" data-target=".nav-tabs">
<we-button data-add-tab="1" data-no-preview="true">Add Tab</we-button>
<we-button data-remove-tab="1" data-no-preview="true">Remove Tab</we-button>
<we-button data-remove-tab="1" data-name="remove_tab_opt" data-no-preview="true">Remove Tab</we-button>
</div>
<div data-js="gallery" data-selector=".o_gallery">
@@ -1179,11 +1179,11 @@
<we-divider/>
<we-select string="Mode">
<we-button data-mode="nomode">Float</we-button>
<we-button data-mode="masonry">Masonry</we-button>
<we-button data-mode="grid">Grid</we-button>
<we-button data-mode="slideshow">Slideshow</we-button>
<we-button data-mode="masonry" data-name="masonry_mode_opt">Masonry</we-button>
<we-button data-mode="grid" data-name="grid_mode_opt">Grid</we-button>
<we-button data-mode="slideshow" data-name="slideshow_mode_opt">Slideshow</we-button>
</we-select>
<we-select string="Slideshow speed">
<we-select string="Slideshow speed" data-dependencies="slideshow_mode_opt">
<we-button data-interval="1000">1s</we-button>
<we-button data-interval="2000">2s</we-button>
<we-button data-interval="3000">3s</we-button>
@@ -1191,7 +1191,7 @@
<we-button data-interval="10000">10s</we-button>
<we-button data-interval="0">Disable autoplay</we-button>
</we-select>
<we-select string="Columns">
<we-select string="Columns" data-dependencies="masonry_mode_opt, grid_mode_opt">
<we-button data-columns="1">1</we-button>
<we-button data-columns="2">2</we-button>
<we-button data-columns="3">3</we-button>
@@ -1199,7 +1199,7 @@
<we-button data-columns="6">6</we-button>
<we-button data-columns="12">12</we-button>
</we-select>
<we-select string="Images spacing" class="o_w_image_spacing_option">
<we-select string="Images spacing" data-dependencies="!slideshow_mode_opt">
<we-button data-select-class="o_spc-none">None</we-button>
<we-button data-select-class="o_spc-small">Small</we-button>
<we-button data-select-class="o_spc-medium">Medium</we-button>
@@ -1282,12 +1282,12 @@
<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-button data-background-type="repeat-pattern" data-name="background_repeat_opt">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-multi data-css-property="background-size">
<we-multi data-css-property="background-size" data-dependencies="background_repeat_opt">
<we-input string="Width" data-select-style="auto" data-unit="px"/>
<we-input string="Height" data-select-style="auto" data-unit="px"/>
</we-multi>
@@ -1338,12 +1338,14 @@
<div data-selector="section .row > div"
data-exclude=".s_col_no_bgcolor, .s_col_no_bgcolor.row > div, .o_gallery .row > div">
<we-row string="Border" class="o_we_inline">
<we-colorpicker data-select-style="true" data-css-property="border-color" data-color-prefix="border-"/>
<we-input class="o_we_inline"
<we-colorpicker data-dependencies="border_width_opt"
data-select-style="true" data-css-property="border-color" data-color-prefix="border-"/>
<we-input class="o_we_inline" data-name="border_width_opt"
data-select-style="0" data-css-property="border-width"
data-unit="px" data-extra-class="border"/>
</we-row>
<we-input string="Rounded" class="o_we_inline"
data-dependencies="border_width_opt"
data-select-style="0" data-css-property="border-radius"
data-unit="px" data-extra-class="rounded"/>
<we-checkbox string="Shadow" data-select-class="shadow"/>
@@ -1421,41 +1423,41 @@
<div data-js="CoverProperties" data-selector=".o_record_cover_container" data-no-check="true">
<we-button data-change="true" data-no-preview="true">Change Cover</we-button>
<we-button data-clear="true" data-no-preview="true">Remove Cover</we-button>
<we-select string="Size">
<we-button data-cover-opt="size" data-select-class="o_record_has_cover cover_full">Full Screen</we-button>
<we-button data-cover-opt="size" class="o_record_cover_opt_size_default" data-select-class="o_record_has_cover cover_mid">Half Screen</we-button>
<we-button data-cover-opt="size" data-select-class="o_record_has_cover cover_auto">Fit text</we-button>
<we-button data-clear="true" data-no-preview="true" data-name="remove_cover_opt">Remove Cover</we-button>
<we-select string="Size" data-cover-opt-name="size">
<we-button data-select-class="o_record_has_cover cover_full">Full Screen</we-button>
<we-button class="o_record_cover_opt_size_default" data-select-class="o_record_has_cover cover_mid">Half Screen</we-button>
<we-button data-select-class="o_record_has_cover cover_auto">Fit text</we-button>
</we-select>
<we-select string="Filter Intensity">
<we-button data-cover-opt="filters" data-filter-value="0.0">None</we-button>
<we-button data-cover-opt="filters" data-filter-value="0.2">Low</we-button>
<we-button data-cover-opt="filters" data-filter-value="0.4">Medium</we-button>
<we-button data-cover-opt="filters" data-filter-value="0.6">High</we-button>
<we-select string="Filter Intensity" data-cover-opt-name="filters">
<we-button data-filter-value="0.0">None</we-button>
<we-button data-filter-value="0.2">Low</we-button>
<we-button data-filter-value="0.4">Medium</we-button>
<we-button data-filter-value="0.6">High</we-button>
</we-select>
<we-select string="Filter Color">
<we-button data-cover-opt="filters" data-filter-color="oe_black">Black</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_none">White</we-button>
<we-button data-cover-opt="filters" data-filter-color="bg-primary">Primary</we-button>
<we-button data-cover-opt="filters" data-filter-color="bg-secondary">Secondary</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_blue">Blue</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_yellow">Yellow</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_red">Red</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_purple">Purple</we-button>
<we-button data-cover-opt="filters" data-filter-color="oe_green">Green</we-button>
<we-select string="Filter Color" data-cover-opt-name="filters">
<we-button data-filter-color="oe_black">Black</we-button>
<we-button data-filter-color="oe_none">White</we-button>
<we-button data-filter-color="bg-primary">Primary</we-button>
<we-button data-filter-color="bg-secondary">Secondary</we-button>
<we-button data-filter-color="oe_blue">Blue</we-button>
<we-button data-filter-color="oe_yellow">Yellow</we-button>
<we-button data-filter-color="oe_red">Red</we-button>
<we-button data-filter-color="oe_purple">Purple</we-button>
<we-button data-filter-color="oe_green">Green</we-button>
</we-select>
<we-select string="Text Size">
<we-button data-cover-opt="text_size" data-select-class="o_record_cover_font_hero">Hero</we-button>
<we-button data-cover-opt="text_size" data-select-class="o_record_cover_font_huge">Huge</we-button>
<we-button data-cover-opt="text_size" data-select-class="o_record_cover_font_big">Big</we-button>
<we-button data-cover-opt="text_size" data-select-class="">Regular</we-button>
<we-button data-cover-opt="text_size" data-select-class="o_record_cover_font_small">Small</we-button>
<we-button data-cover-opt="text_size" data-select-class="o_record_cover_font_tiny">Tiny</we-button>
<we-select string="Text Size" data-cover-opt-name="text_size">
<we-button data-select-class="o_record_cover_font_hero">Hero</we-button>
<we-button data-select-class="o_record_cover_font_huge">Huge</we-button>
<we-button data-select-class="o_record_cover_font_big">Big</we-button>
<we-button data-select-class="">Regular</we-button>
<we-button data-select-class="o_record_cover_font_small">Small</we-button>
<we-button data-select-class="o_record_cover_font_tiny">Tiny</we-button>
</we-select>
<we-select string="Text Alignment">
<we-button data-cover-opt="text_align" data-select-class="">Left</we-button>
<we-button data-cover-opt="text_align" data-select-class="text-center">Centered</we-button>
<we-button data-cover-opt="text_align" data-select-class="text-right">Right</we-button>
<we-select string="Text Alignment" data-cover-opt-name="text_align">
<we-button data-select-class="">Left</we-button>
<we-button data-select-class="text-center">Centered</we-button>
<we-button data-select-class="text-right">Right</we-button>
</we-select>
</div>
@@ -1471,14 +1473,16 @@
<we-datetimepicker string="Due Date" data-select-data-attribute="" data-attribute-name="endTime"/>
<we-row>
<we-select string="What should happen at the end?" data-no-preview="true">
<we-button data-end-action="nothing">Nothing</we-button>
<we-button data-end-action="redirect">Redirect</we-button>
<we-button data-end-action="nothing" data-name="no_end_action_opt">Nothing</we-button>
<we-button data-end-action="redirect" data-name="redirect_end_action_opt">Redirect</we-button>
<we-button data-end-action="message">Show Message</we-button>
</we-select>
<we-button class="far fa-edit fa-fw align-self-end toggle-edit-message" title="Edit Message"/>
</we-row>
<we-checkbox string="Hide countdown at the end" data-select-class="hide-countdown"/>
<we-urlpicker string="URL" data-select-data-attribute="/my-awesome-page" data-attribute-name="redirectUrl"/>
<we-checkbox string="Hide countdown at the end" data-dependencies="!no_end_action_opt"
data-select-class="hide-countdown"/>
<we-urlpicker string="URL" data-dependencies="redirect_end_action_opt"
data-select-data-attribute="/my-awesome-page" data-attribute-name="redirectUrl"/>
<we-select string="Size" data-attribute-name="size">
<we-button data-select-data-attribute="80">Small</we-button>
<we-button data-select-data-attribute="120">Medium</we-button>
@@ -1491,27 +1495,29 @@
</we-select>
<we-colorpicker string="Text Color" data-attribute-name="textColor" data-select-data-attribute=""/>
<we-select string="Layout">
<we-button data-layout="circle">Circle</we-button>
<we-button data-layout="boxes">Boxes</we-button>
<we-button data-layout="circle" data-name="circle_layout_opt">Circle</we-button>
<we-button data-layout="boxes" data-name="boxes_layout_opt">Boxes</we-button>
<we-button data-layout="clean">Clean</we-button>
<we-button data-layout="text">Text Inline</we-button>
</we-select>
<we-select string="Layout Background" data-attribute-name="layoutBackground">
<we-select string="Layout Background" data-attribute-name="layoutBackground" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="inner">Inner</we-button>
<we-button data-select-data-attribute="plain">Plain</we-button>
<we-button data-select-data-attribute="none">None</we-button>
<we-button data-select-data-attribute="none" data-name="no_background_layout_opt">None</we-button>
</we-select>
<we-colorpicker string="Layout Background Color" data-attribute-name="layoutBackgroundColor" data-select-data-attribute=""/>
<we-select string="Progress Bar Style" data-attribute-name="progressBarStyle">
<we-colorpicker string="Layout Background Color" data-dependencies="!no_background_layout_opt"
data-attribute-name="layoutBackgroundColor" data-select-data-attribute=""/>
<we-select string="Progress Bar Style" data-attribute-name="progressBarStyle" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="surrounded">Surrounded</we-button>
<we-button data-select-data-attribute="disappear">Disappearing</we-button>
<we-button data-select-data-attribute="none">None</we-button>
<we-button data-select-data-attribute="none" data-name="no_progressbar_style_opt">None</we-button>
</we-select>
<we-select string="Progress Bar Weight" data-attribute-name="progressBarWeight">
<we-select string="Progress Bar Weight" data-attribute-name="progressBarWeight" data-dependencies="!no_progressbar_style_opt">
<we-button data-select-data-attribute="thin">Thin</we-button>
<we-button data-select-data-attribute="thick">Thick</we-button>
</we-select>
<we-colorpicker string="Progress Bar Color" data-attribute-name="progressBarColor" data-select-data-attribute=""/>
<we-colorpicker string="Progress Bar Color" data-dependencies="!no_progressbar_style_opt"
data-attribute-name="progressBarColor" data-select-data-attribute=""/>
</div>
<!-- Chart Inner -->
@@ -1522,14 +1528,15 @@
data-css-property="background-color"
data-color-prefix="bg-"/>
<we-select string="Type" data-attribute-name="type" data-attribute-default-value="bar" data-no-preview="true">
<we-button data-select-data-attribute="bar">Bar Vertical</we-button>
<we-button data-select-data-attribute="horizontalBar">Bar Horizontal</we-button>
<we-button data-select-data-attribute="bar" data-name="bar_chart_opt">Bar Vertical</we-button>
<we-button data-select-data-attribute="horizontalBar" data-name="horizontal_bar_chart_opt">Bar Horizontal</we-button>
<we-button data-select-data-attribute="line">Line</we-button>
<we-button data-select-data-attribute="pie">Pie</we-button>
<we-button data-select-data-attribute="doughnut">Doughnut</we-button>
<we-button data-select-data-attribute="radar">Radar</we-button>
</we-select>
<we-checkbox string="Stacked" data-select-data-attribute="false|true" data-attribute-name="stacked" data-no-preview="true"/>
<we-checkbox string="Stacked" data-name="stacked_chart_opt" data-dependencies="bar_chart_opt, horizontal_bar_chart_opt"
data-select-data-attribute="false|true" data-attribute-name="stacked" data-no-preview="true"/>
<we-select string="Legend" data-attribute-name="legendPosition" data-attribute-default-value="top" data-no-preview="true">
<we-button data-select-data-attribute="none">None</we-button>
<we-button data-select-data-attribute="top">Top</we-button>
@@ -1549,11 +1556,11 @@
</tr>
</table>
</we-matrix>
<we-colorpicker string="Background"
<we-colorpicker string="Background" data-name="chart_bg_color_opt"
data-color-change=""
data-attribute-name="backgroundColor"
data-no-preview="true"/>
<we-colorpicker string="Border"
<we-colorpicker string="Border" data-name="chart_border_color_opt"
data-color-change=""
data-attribute-name="borderColor"
data-no-preview="true"/>