[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:
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user