From e82a1cb2ef1bcb413d99e7eb7521405d1d2e88d2 Mon Sep 17 00:00:00 2001 From: "Louis (loco)" Date: Tue, 10 Jan 2023 16:16:40 +0000 Subject: [PATCH] [IMP] website: polish the configurator and save info in IAP database The first objective of this commit is to polish the website configurator: 1) On page 1 of it: - The size of the text is increased. 2) On page 2 of it: - The font size inside dropdown is reduced. - 30 results are displayed inside dropdown instead of 15. 3) On page 3 of it: - The size of the text is increased. 3) On page 4: - The cards are refactored. - The end line is removed. - The "Build my website" button is moved to the left. 4) On page 5 : - It is now impossible for the user to click on a theme preview before it is completely loaded. 5) On all pages, the size of the "Skip and start from scratch" button has been increased. On the second page of the website configurator, the user has to choose an industry type. Sometimes, it happens that the user is searching for an industry that is not known by the server. Before this commit, a message of type "No result found" appeared and the user had to choose an other industry among the existing ones. From this commit, the "No result found" message is replaced by the name of the industry that the user is writing. To choose its industry type, the user can click on the displayed industry name or outside of the input area. This last option calls the `_blurIndustrySelection` function and the written industry is kept as the final industry choice. If the user chooses an industry name that is not known by the server then the remaining of the website configuration is done with the "abbey" industry. Moreover, this unknown industry name as well as the language used by the user is sent and stored to the IAP database. The goal of it is to regularly update the industry list so that it becomes as most complete as possible. IAP PR : https://github.com/odoo/iap-apps/pull/549 task-3062171 closes odoo/odoo#110187 Signed-off-by: Romain Derie (rde) --- addons/website/models/website.py | 10 ++ .../configurator/configurator.js | 114 ++++++++---------- .../configurator/configurator.scss | 2 +- .../configurator/configurator.xml | 32 ++--- 4 files changed, 79 insertions(+), 79 deletions(-) diff --git a/addons/website/models/website.py b/addons/website/models/website.py index f2cb46a9238..4688ac5ee75 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -386,6 +386,16 @@ class Website(models.Model): website = self.get_current_website() website.configurator_done = True + @api.model + def configurator_missing_industry(self, unknown_industry): + self._website_api_rpc( + '/api/website/unknown_industry', + { + 'unknown_industry': unknown_industry, + 'lang': self.env.context.get('lang'), + } + ) + @api.model def configurator_apply(self, **kwargs): def set_colors(selected_palette): diff --git a/addons/website/static/src/client_actions/configurator/configurator.js b/addons/website/static/src/client_actions/configurator/configurator.js index 7d0858f1780..c62521e5aae 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.js +++ b/addons/website/static/src/client_actions/configurator/configurator.js @@ -4,7 +4,7 @@ import concurrency from 'web.concurrency'; import utils from 'web.utils'; import weUtils from 'web_editor.utils'; import {ColorpickerWidget} from 'web.Colorpicker'; -import {_t, _lt} from 'web.core'; +import {_lt} from 'web.core'; import {svgToPNG} from 'website.utils'; import { useService } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; @@ -89,6 +89,7 @@ class DescriptionScreen extends Component { this.state = useStore(); this.labelToId = {}; this.autocompleteHasResults = true; + this.orm = useService('orm'); onMounted(() => this.onMounted()); } @@ -101,8 +102,6 @@ class DescriptionScreen extends Component { minLength: 1, source: this._autocompleteSearch.bind(this), select: this._selectIndustry.bind(this), - open: this._customizeNoResultMenuStyle.bind(this), - focus: this._disableKeyboardNav.bind(this), classes: { 'ui-autocomplete': 'custom-ui-autocomplete shadow-lg border-0 o_configurator_show_fast o_configurator_industry_dropdown', }, @@ -114,17 +113,6 @@ class DescriptionScreen extends Component { } } - /** - * Clear the input and its parent label and set the selected industry to undefined. - * - * @private - */ - _clearIndustrySelection() { - this.industrySelection.el.value = ''; - this.industrySelection.el.parentNode.dataset.value = ''; - this.state.selectIndustry(); - } - /** * Set the input's parent label value to automatically adapt input size * and update the selected industry. @@ -134,37 +122,17 @@ class DescriptionScreen extends Component { */ _setSelectedIndustry(label) { this.industrySelection.el.parentNode.dataset.value = label; - const id = this.labelToId[label]; - this.state.selectIndustry(label, id); + if (!this.autocompleteHasResults) { + // If no industry has been found, the first one is chosen by + // default to continue the website configuration. + this.state.selectIndustry(label, 1); + } else { + const id = this.labelToId[label]; + this.state.selectIndustry(label, id); + } this.checkDescriptionCompletion(); } - /** - * Called each time the suggestion menu is opened or updated. If there are no - * results to display the style of the "No result found" message is customized. - * - * @private - */ - _customizeNoResultMenuStyle() { - if (!this.autocompleteHasResults) { - const noResultLinkEl = this.industrySelection.el.parentElement.getElementsByTagName('a')[0]; - noResultLinkEl.classList.add('o_no_result'); - } - } - - /** - * Disables keyboard navigation when there are no results to avoid selecting the - * "No result found" message by pressing the down arrow key. - * - * @private - * @param {Event} ev - */ - _disableKeyboardNav(ev) { - if (!this.autocompleteHasResults) { - ev.preventDefault(); - } - } - /** * Called each time the autocomplete input's value changes. Only industries * having a label or a synonym containing all terms of the input value are @@ -172,7 +140,7 @@ class DescriptionScreen extends Component { * The order received from IAP is kept (expected to be on descending hit * count) unless there are 7 or less matches in which case the results are * sorted alphabetically. - * The result size is limited to 15. + * The result size is limited to 30. * * @param {Object} request object with a single 'term' property which is the * input current value @@ -181,7 +149,7 @@ class DescriptionScreen extends Component { */ _autocompleteSearch(request, response) { const terms = request.term.toLowerCase().split(/[|,\n]+/); - const limit = 15; + const limit = 30; const sortLimit = 7; // `this.state.industries` is already sorted by hit count (from IAP). // That order should be kept after manipulating the recordset. @@ -196,7 +164,7 @@ class DescriptionScreen extends Component { }); if (matches.length > limit) { // Keep matches with the least number of words so that e.g. - // "restaurant" remains available even if there are 15 specific + // "restaurant" remains available even if there are 30 specific // sub-types that have a higher hit count. matches = matches.sort((x, y) => x.wordCount - y.wordCount) .slice(0, limit) @@ -215,14 +183,13 @@ class DescriptionScreen extends Component { this.labelToId[r.label] = r.id; }); } else { - labels = [_t("No result found, broaden your search.")]; + labels = [request.term]; } response(labels); } /** - * Called when a menu option is selected. Update the selected industry or - * clear the input if the option is the "No result found" message. + * Called when a menu option is selected. Update the selected industry. * * @private * @param {Event} ev @@ -230,27 +197,19 @@ class DescriptionScreen extends Component { * the selected option. */ _selectIndustry(ev, ui) { - if (this.autocompleteHasResults) { - this._setSelectedIndustry(ui.item.label); - } else { - this._clearIndustrySelection(); - ev.preventDefault(); - } + this._setSelectedIndustry(ui.item.label); } /** - * Called on industrySelection input blur. Updates the selected industry or - * clears the input if its current value is not a valid industry. + * Called on industrySelection input blur. Updates the selected industry. * * @private * @param {Event} ev */ _blurIndustrySelection(ev) { - if (this.labelToId[ev.target.value] !== undefined) { - this._setSelectedIndustry(ev.target.value); - } else { - this._clearIndustrySelection(); - } + // Check if the input is known by the server. + this.autocompleteHasResults = !!this.labelToId[ev.target.value]; + this._setSelectedIndustry(ev.target.value); } selectWebsiteType(id) { @@ -269,6 +228,13 @@ class DescriptionScreen extends Component { checkDescriptionCompletion() { const {selectedType, selectedPurpose, selectedIndustry} = this.state; if (selectedType && selectedPurpose && selectedIndustry) { + // If the industry name is not known by the server, send it to the + // IAP server. + if (!this.autocompleteHasResults) { + this.orm.call('website', 'configurator_missing_industry', [], { + 'unknown_industry': selectedIndustry.label, + }); + } this.props.navigate(ROUTES.paletteSelectionScreen); } } @@ -451,13 +417,35 @@ class ThemeSelectionScreen extends ApplyConfiguratorScreen { setup() { super.setup(); + this.uiService = useService('ui'); this.orm = useService('orm'); this.state = useStore(); this.themeSVGPreviews = [useRef('ThemePreview1'), useRef('ThemePreview2'), useRef('ThemePreview3')]; + const proms = []; - onMounted(() => { + onMounted(async () => { + // Add a loading effect during the loading of the images inside the + // svgs. + this.uiService.block(); this.state.themes.forEach((theme, idx) => { - $(this.themeSVGPreviews[idx].el).append(theme.svg); + // Transform the text svg into a svg element. + const svgEl = new DOMParser().parseFromString(theme.svg, 'image/svg+xml').documentElement; + for (const imgEl of svgEl.querySelectorAll('image')) { + proms.push(new Promise((resolve, reject) => { + imgEl.addEventListener('load', () => { + resolve(imgEl); + }, {once: true}); + imgEl.addEventListener('error', () => { + reject(imgEl); + }, {once: true}); + })); + } + $(this.themeSVGPreviews[idx].el).append(svgEl); + }); + // When all the images inside the svgs are loaded then remove the + // loading effect. + Promise.allSettled(proms).then(() => { + this.uiService.unblock(); }); }); } diff --git a/addons/website/static/src/client_actions/configurator/configurator.scss b/addons/website/static/src/client_actions/configurator/configurator.scss index 4b73ac8c0c6..0837d35acf5 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.scss +++ b/addons/website/static/src/client_actions/configurator/configurator.scss @@ -171,7 +171,7 @@ } .dropdown-item, .fa-angle-down, .ui-menu-item a, .ui-menu-item a.ui-state-active { - font-size: .65em; + font-size: .5em; font-weight: inherit; } diff --git a/addons/website/static/src/client_actions/configurator/configurator.xml b/addons/website/static/src/client_actions/configurator/configurator.xml index fa34fd78239..8ef806f8d58 100644 --- a/addons/website/static/src/client_actions/configurator/configurator.xml +++ b/addons/website/static/src/client_actions/configurator/configurator.xml @@ -3,7 +3,7 @@
- +
@@ -12,10 +12,10 @@