From bfec4e2ca41659c5e24204305b85b10e7b44c366 Mon Sep 17 00:00:00 2001 From: Benjamin Vray Date: Mon, 15 Nov 2021 15:25:16 +0000 Subject: [PATCH] [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) --- addons/website/controllers/main.py | 40 ++++----- addons/website/models/ir_asset.py | 31 +++++++ addons/website/models/theme_models.py | 10 +-- .../static/src/js/editor/snippets.options.js | 85 ++++++++++++------- addons/website/views/snippets/snippets.xml | 2 +- addons/website/views/website_templates.xml | 2 + 6 files changed, 114 insertions(+), 56 deletions(-) diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index ef193673f3c..a3db86d7a08 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -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): diff --git a/addons/website/models/ir_asset.py b/addons/website/models/ir_asset.py index acba68f00e4..ddaba61b6e2 100644 --- a/addons/website/models/ir_asset.py +++ b/addons/website/models/ir_asset.py @@ -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 diff --git a/addons/website/models/theme_models.py b/addons/website/models/theme_models.py index 526dff97f8f..adcaea7c227 100644 --- a/addons/website/models/theme_models.py +++ b/addons/website/models/theme_models.py @@ -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) diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 1454fc79d3a..4ca29d192e2 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -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 diff --git a/addons/website/views/snippets/snippets.xml b/addons/website/views/snippets/snippets.xml index 9588d51bd17..33bef78b838 100644 --- a/addons/website/views/snippets/snippets.xml +++ b/addons/website/views/snippets/snippets.xml @@ -1355,7 +1355,7 @@ diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index fc5e007599a..e7c59c75f75 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -2042,6 +2042,7 @@ + website.ripple_effect_scss Ripple effect SCSS web.assets_frontend /website/static/src/scss/options/ripple_effect.scss @@ -2049,6 +2050,7 @@ + website.ripple_effect_js Ripple effect JS web.assets_frontend /website/static/src/js/content/ripple_effect.js