odoo.define('website.snippet.editor', function (require) { 'use strict'; const {qweb, _t, _lt} = require('web.core'); const Dialog = require('web.Dialog'); const weSnippetEditor = require('web_editor.snippet.editor'); const wSnippetOptions = require('website.editor.snippets.options'); const FontFamilyPickerUserValueWidget = wSnippetOptions.FontFamilyPickerUserValueWidget; weSnippetEditor.Class.include({ xmlDependencies: (weSnippetEditor.Class.prototype.xmlDependencies || []) .concat(['/website/static/src/xml/website.editor.xml']), events: _.extend({}, weSnippetEditor.Class.prototype.events, { 'click .o_we_customize_theme_btn': '_onThemeTabClick', }), custom_events: Object.assign({}, weSnippetEditor.Class.prototype.custom_events, { 'gmap_api_request': '_onGMapAPIRequest', 'gmap_api_key_request': '_onGMapAPIKeyRequest', }), tabs: _.extend({}, weSnippetEditor.Class.prototype.tabs, { THEME: 'theme', }), optionsTabStructure: [ ['theme-colors', _lt("Theme Colors")], ['theme-options', _lt("Theme Options")], ['website-settings', _lt("Website Settings")], ], //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * @override */ _computeSnippetTemplates: function (html) { const $html = $(html); const fontVariables = _.map($html.find('we-fontfamilypicker[data-variable]'), el => { return el.dataset.variable; }); FontFamilyPickerUserValueWidget.prototype.fontVariables = fontVariables; return this._super(...arguments); }, /** * Depending of the demand, reconfigure they gmap key or configure it * if not already defined. * * @private * @param {boolean} [reconfigure=false] * @param {boolean} [onlyIfUndefined=false] */ async _configureGMapAPI({reconfigure, onlyIfUndefined}) { const apiKey = await new Promise(resolve => { this.getParent().trigger_up('gmap_api_key_request', { onSuccess: key => resolve(key), }); }); if (!reconfigure && (apiKey || !onlyIfUndefined)) { return false; } let websiteId; this.trigger_up('context_get', { callback: ctx => websiteId = ctx['website_id'], }); return new Promise(resolve => { let invalidated = false; const dialog = new Dialog(this, { size: 'medium', title: _t("Google Map API Key"), buttons: [ {text: _t("Save"), classes: 'btn-primary', close: true, click: async () => { const newAPIKey = dialog.$('#api_key_input').val() || false; await this._rpc({ model: 'website', method: 'write', args: [ [websiteId], {google_maps_api_key: newAPIKey}, ], }); invalidated = true; }}, {text: _t("Cancel"), close: true} ], $content: $(qweb.render('website.s_google_map_modal', { apiKey: apiKey, })), }); dialog.on('closed', this, () => resolve(invalidated)); dialog.open(); }); }, /** * @override */ _getScrollOptions(options = {}) { const finalOptions = this._super(...arguments); if (!options.offsetElements || !options.offsetElements.$top) { const $header = $('#top'); if ($header.length) { finalOptions.offsetElements = finalOptions.offsetElements || {}; finalOptions.offsetElements.$top = $header; } } return finalOptions; }, /** * @private * @param {OdooEvent} ev * @param {string} gmapRequestEventName */ async _handleGMapRequest(ev, gmapRequestEventName) { ev.stopPropagation(); const reconfigured = await this._configureGMapAPI({ reconfigure: ev.data.reconfigure, onlyIfUndefined: ev.data.configureIfNecessary, }); this.getParent().trigger_up(gmapRequestEventName, { refetch: reconfigured, editableMode: true, onSuccess: key => ev.data.onSuccess(key), }); }, /** * @override */ _updateLeftPanelContent: function ({content, tab}) { this._super(...arguments); this.$('.o_we_customize_theme_btn').toggleClass('active', tab === this.tabs.THEME); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * @private * @param {OdooEvent} ev */ _onGMapAPIRequest(ev) { this._handleGMapRequest(ev, 'gmap_api_request'); }, /** * @private * @param {OdooEvent} ev */ _onGMapAPIKeyRequest(ev) { this._handleGMapRequest(ev, 'gmap_api_key_request'); }, /** * @private */ async _onThemeTabClick(ev) { // Note: nothing async here but start the loading effect asap this._execWithLoadingEffect(async () => new Promise(resolve => setTimeout(() => resolve(), 0)), false, 0); if (!this.topFakeOptionEl) { let el; for (const [elementName, title] of this.optionsTabStructure) { const newEl = document.createElement(elementName); newEl.dataset.name = title; if (el) { el.appendChild(newEl); } else { this.topFakeOptionEl = newEl; } el = newEl; } this.bottomFakeOptionEl = el; this.el.appendChild(this.topFakeOptionEl); } // Need all of this in that order so that: // - the element is visible and can be enabled and the onFocus method is // called each time. // - the element is hidden afterwards so it does not take space in the // DOM, same as the overlay which may make a scrollbar appear. this.topFakeOptionEl.classList.remove('d-none'); const editor = await this._activateSnippet($(this.bottomFakeOptionEl)); this.topFakeOptionEl.classList.add('d-none'); editor.toggleOverlay(false); this._updateLeftPanelContent({ tab: this.tabs.THEME, }); }, }); });