[FIX] website: fix ripple effect on buttons

Before this commit, the "ripple effect" no longer worked because the
assets were never activated for the following reason:

- To activate the ripple effect assets via the editor options, we
activated a template that no longer exists (with
data-customize-website-views). Instead of activating the assets with the
new system of assets using records.

After this commit, a new "data-customize-website-assets" xml attribute
was created so that the assets can enable/disable in the same way as the
views. The "write" method for ir.asset has also been overridden in
website so that each website has its specific assets (via COW).

task-2686370

closes odoo/odoo#81833

X-original-commit: 9f56357cc1f4a7b8606ef4d5fd431fc396bdf1e8
Related: odoo/design-themes#546
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
Benjamin Vray
2021-12-23 15:07:06 +00:00
committed by qsm-odoo
parent 50abe8947a
commit bfec4e2ca4
6 changed files with 114 additions and 56 deletions
+21 -19
View File
@@ -692,32 +692,34 @@ class Website(Home):
# Themes
# ------------------------------------------------------
def _get_customize_views(self, xml_ids):
View = request.env["ir.ui.view"].with_context(active_test=False)
if not xml_ids:
return View
domain = [("key", "in", xml_ids)] + request.website.website_domain()
return View.search(domain).filter_duplicate()
def _get_customize_data(self, keys, isViewData):
model = 'ir.ui.view' if isViewData else 'ir.asset'
Data = request.env[model].with_context(active_test=False)
if not keys:
return Data
domain = [("key", "in", keys)] + request.website.website_domain()
return Data.search(domain).filter_duplicate()
@http.route(['/website/theme_customize_get'], type='json', auth='user', website=True)
def theme_customize_get(self, xml_ids):
views = self._get_customize_views(xml_ids)
return views.filtered('active').mapped('key')
@http.route(['/website/theme_customize_data_get'], type='json', auth='user', website=True)
def theme_customize_data_get(self, keys, isViewData):
Data = self._get_customize_data(keys, isViewData)
return Data.filtered('active').mapped('key')
@http.route(['/website/theme_customize'], type='json', auth='user', website=True)
def theme_customize(self, enable=None, disable=None, reset_view_arch=False):
@http.route(['/website/theme_customize_data'], type='json', auth='user', website=True)
def theme_customize_data(self, isViewData, enable=None, disable=None, reset_view_arch=False):
"""
Enables and/or disables views according to list of keys.
Enables and/or disables views/assets according to list of keys.
:param enable: list of views' keys to enable
:param disable: list of views' keys to disable
:param isViewData: True = "ir.ui.view", False = "ir.asset"
:param enable: list of views/assets keys to enable
:param disable: list of views/assets keys to disable
:param reset_view_arch: restore the default template after disabling
"""
disabled_views = self._get_customize_views(disable).filtered('active')
disabled_data = self._get_customize_data(disable, isViewData).filtered('active')
if reset_view_arch:
disabled_views.reset_arch(mode='hard')
disabled_views.write({'active': False})
self._get_customize_views(enable).filtered(lambda x: not x.active).write({'active': True})
disabled_data.reset_arch(mode='hard')
disabled_data.write({'active': False})
self._get_customize_data(enable, isViewData).filtered(lambda x: not x.active).write({'active': True})
@http.route(['/website/theme_customize_bundle_reload'], type='json', auth='user', website=True)
def theme_customize_bundle_reload(self):
+31
View File
@@ -58,3 +58,34 @@ class IrAsset(models.Model):
most_specific_assets += asset
return most_specific_assets
def write(self, vals):
"""COW for ir.asset. This way editing websites does not impact other
websites. Also this way newly created websites will only
contain the default assets.
"""
current_website_id = self.env.context.get('website_id')
if not current_website_id or self.env.context.get('no_cow'):
return super().write(vals)
for asset in self.with_context(active_test=False):
# No need of COW if the asset is already specific
if asset.website_id:
super(IrAsset, asset).write(vals)
continue
# If already a specific asset for this generic asset, write on it
website_specific_asset = asset.search([
('key', '=', asset.key),
('website_id', '=', current_website_id)
], limit=1)
if website_specific_asset:
super(IrAsset, website_specific_asset).write(vals)
continue
copy_vals = {'website_id': current_website_id, 'key': asset.key}
website_specific_asset = asset.copy(copy_vals)
super(IrAsset, website_specific_asset).write(vals)
return True
+5 -5
View File
@@ -221,8 +221,8 @@ class Theme(models.AbstractModel):
)
# Reinitialize effets
self.disable_asset('website.ripple_effect_scss')
self.disable_asset('website.ripple_effect_js')
self.disable_asset("website.ripple_effect_scss")
self.disable_asset("website.ripple_effect_js")
# Reinitialize header templates
self.disable_view('website.template_header_hamburger')
@@ -251,15 +251,15 @@ class Theme(models.AbstractModel):
self.disable_view('website.option_footer_scrolltop')
@api.model
def _toggle_asset(self, name, active):
def _toggle_asset(self, key, active):
ThemeAsset = self.env['theme.ir.asset'].sudo().with_context(active_test=False)
obj = ThemeAsset.search([('name', '=', name)])
obj = ThemeAsset.search([('key', '=', key)])
website = self.env['website'].get_current_website()
if obj:
obj = obj.copy_ids.filtered(lambda x: x.website_id == website)
else:
Asset = self.env['ir.asset'].sudo().with_context(active_test=False)
obj = Asset.search([('name', '=', name)])
obj = Asset.search([('key', '=', key)], limit=1)
has_specific = obj.key and Asset.search_count([
('key', '=', obj.key),
('website_id', '=', website.id)
@@ -428,6 +428,12 @@ options.Class.include({
customizeWebsiteColor: async function (previewMode, widgetValue, params) {
await this._customizeWebsite(previewMode, widgetValue, params, 'color');
},
/**
* @see this.selectClass for parameters
*/
async customizeWebsiteAssets(previewMode, widgetValue, params) {
await this._customizeWebsite(previewMode, widgetValue, params, 'assets');
},
//--------------------------------------------------------------------------
// Private
@@ -474,24 +480,7 @@ options.Class.include({
_computeWidgetState: async function (methodName, params) {
switch (methodName) {
case 'customizeWebsiteViews': {
const allXmlIDs = this._getXMLIDsFromPossibleValues(params.possibleValues);
const enabledXmlIDs = await this._rpc({
route: '/website/theme_customize_get',
params: {
'xml_ids': allXmlIDs,
},
});
let mostXmlIDsStr = '';
let mostXmlIDsNb = 0;
for (const xmlIDsStr of params.possibleValues) {
const enableXmlIDs = xmlIDsStr.split(/\s*,\s*/);
if (enableXmlIDs.length > mostXmlIDsNb
&& enableXmlIDs.every(xmlID => enabledXmlIDs.includes(xmlID))) {
mostXmlIDsStr = xmlIDsStr;
mostXmlIDsNb = enableXmlIDs.length;
}
}
return mostXmlIDsStr; // Need to return the exact same string as in possibleValues
return this._getEnabledCustomizeValues(params.possibleValues, true);
}
case 'customizeWebsiteVariable': {
return weUtils.getCSSVariableValue(params.variable);
@@ -499,6 +488,9 @@ options.Class.include({
case 'customizeWebsiteColor': {
return weUtils.getCSSVariableValue(params.color);
}
case 'customizeWebsiteAssets': {
return this._getEnabledCustomizeValues(params.possibleValues, false);
}
}
return this._super(...arguments);
},
@@ -513,7 +505,7 @@ options.Class.include({
switch (type) {
case 'views':
await this._customizeWebsiteViews(widgetValue, params);
await this._customizeWebsiteData(widgetValue, params, true);
break;
case 'variable':
await this._customizeWebsiteVariable(widgetValue, params);
@@ -521,6 +513,9 @@ options.Class.include({
case 'color':
await this._customizeWebsiteColor(widgetValue, params);
break;
case 'assets':
await this._customizeWebsiteData(widgetValue, params, false);
break;
default:
if (params.customCustomization) {
await params.customCustomization.call(this, widgetValue, params);
@@ -585,17 +580,18 @@ options.Class.include({
/**
* @private
*/
_customizeWebsiteViews: async function (xmlID, params) {
const allXmlIDs = this._getXMLIDsFromPossibleValues(params.possibleValues);
const enableXmlIDs = xmlID.split(/\s*,\s*/);
const disableXmlIDs = allXmlIDs.filter(xmlID => !enableXmlIDs.includes(xmlID));
async _customizeWebsiteData(value, params, isViewData) {
const allDataKeys = this._getDataKeysFromPossibleValues(params.possibleValues);
const enableDataKeys = value.split(/\s*,\s*/);
const disableDataKeys = allDataKeys.filter(value => !enableDataKeys.includes(value));
const resetViewArch = !!params.resetViewArch;
return this._rpc({
route: '/website/theme_customize',
route: '/website/theme_customize_data',
params: {
'enable': enableXmlIDs,
'disable': disableXmlIDs,
'isViewData': isViewData,
'enable': enableDataKeys,
'disable': disableDataKeys,
'reset_view_arch': resetViewArch,
},
});
@@ -603,12 +599,39 @@ options.Class.include({
/**
* @private
*/
_getXMLIDsFromPossibleValues: function (possibleValues) {
const allXmlIDs = [];
for (const xmlIDsStr of possibleValues) {
allXmlIDs.push(...xmlIDsStr.split(/\s*,\s*/));
_getDataKeysFromPossibleValues(possibleValues) {
const allDataKeys = [];
for (const dataKeysStr of possibleValues) {
allDataKeys.push(...dataKeysStr.split(/\s*,\s*/));
}
return allXmlIDs.filter((v, i, arr) => arr.indexOf(v) === i);
return allDataKeys.filter((v, i, arr) => arr.indexOf(v) === i);
},
/**
* @private
* @param {Array} possibleValues
* @param {Boolean} isViewData true = "ir.ui.view", false = "ir.asset"
* @returns {String}
*/
async _getEnabledCustomizeValues(possibleValues, isViewData) {
const allDataKeys = this._getDataKeysFromPossibleValues(possibleValues);
const enabledValues = await this._rpc({
route: '/website/theme_customize_data_get',
params: {
'keys': allDataKeys,
'isViewData': isViewData,
},
});
let mostValuesStr = '';
let mostValuesNb = 0;
for (const valuesStr of possibleValues) {
const enableValues = valuesStr.split(/\s*,\s*/);
if (enableValues.length > mostValuesNb
&& enableValues.every(value => enabledValues.includes(value))) {
mostValuesStr = valuesStr;
mostValuesNb = enableValues.length;
}
}
return mostValuesStr; // Need to return the exact same string as in possibleValues
},
/**
* @private
+1 -1
View File
@@ -1355,7 +1355,7 @@
<we-input string="⌙ Large" data-customize-website-variable="" data-variable="btn-border-radius-lg" data-unit="px" data-save-unit="rem"/>
</we-collapse>
<we-checkbox string="Ripple Effect"
data-customize-website-views="website.option_ripple_effect"
data-customize-website-assets="website.ripple_effect_scss, website.ripple_effect_js"
data-customize-website-variable="false|true"
data-variable="btn-ripple"/>
<we-select string="Link Style" data-variable="link-underline">
@@ -2042,6 +2042,7 @@
<!-- Effect options -->
<record id="website.ripple_effect_scss" model="ir.asset">
<field name="key">website.ripple_effect_scss</field>
<field name="name">Ripple effect SCSS</field>
<field name="bundle">web.assets_frontend</field>
<field name="path">/website/static/src/scss/options/ripple_effect.scss</field>
@@ -2049,6 +2050,7 @@
</record>
<record id="website.ripple_effect_js" model="ir.asset">
<field name="key">website.ripple_effect_js</field>
<field name="name">Ripple effect JS</field>
<field name="bundle">web.assets_frontend</field>
<field name="path">/website/static/src/js/content/ripple_effect.js</field>