diff --git a/addons/website/__manifest__.py b/addons/website/__manifest__.py index 3c86cc69a0e94701048b9e6a6f13c3a0dad591e6..d2017df93368493a3e830ddf1d5c4855b68c1c19 100644 --- a/addons/website/__manifest__.py +++ b/addons/website/__manifest__.py @@ -38,7 +38,11 @@ 'demo': [ 'data/website_demo.xml', ], - 'qweb': ['static/src/xml/website.backend.xml'], + 'qweb': [ + 'static/src/xml/website.backend.xml', + 'static/src/xml/website_widget.xml', + 'static/src/xml/theme_preview.xml', + ], 'application': True, 'uninstall_hook': 'uninstall_hook', } diff --git a/addons/website/models/res_config_settings.py b/addons/website/models/res_config_settings.py index 7b52294ddb1552376552dbe2c78b8025a16369a5..4d05f8436b9d24cc2b1a016c912f846d5eceae58 100644 --- a/addons/website/models/res_config_settings.py +++ b/addons/website/models/res_config_settings.py @@ -161,5 +161,6 @@ class ResConfigSettings(models.TransientModel): def install_theme_on_current_website(self): self.website_id._force() - action = self.env.ref('website.theme_install_kanban_action') - return action.read()[0] + action = self.env.ref('website.theme_install_kanban_action').read()[0] + action['target'] = 'main' + return action diff --git a/addons/website/static/src/img/phone.png b/addons/website/static/src/img/phone.png new file mode 100644 index 0000000000000000000000000000000000000000..0570c4d8fe27d837b829779dd665a6fbc89a4d5d Binary files /dev/null and b/addons/website/static/src/img/phone.png differ diff --git a/addons/website/static/src/js/theme_preview_form.js b/addons/website/static/src/js/theme_preview_form.js new file mode 100644 index 0000000000000000000000000000000000000000..76ce0f3f9f83ca52ba44d6592280bdf4c0315dda --- /dev/null +++ b/addons/website/static/src/js/theme_preview_form.js @@ -0,0 +1,111 @@ +odoo.define('website.theme_preview_form', function (require) { +"use strict"; + +var FormController = require('web.FormController'); +var FormView = require('web.FormView'); +var viewRegistry = require('web.view_registry'); +var core = require('web.core'); +var _t = core._t; +var qweb = core.qweb; + +var ThemePreviewController = FormController.extend({ + events: Object.assign({}, FormController.prototype.events, { + 'click .o_use_theme': '_onUseThemeClick', + 'click .o_switch_theme': '_onSwitchThemeClick', + 'click .o_switch_mode_button': '_onSwitchButtonClick', + }), + /** + * @override + */ + start: function () { + this.$el.addClass('o_view_form_theme_preview_controller'); + return this._super.apply(this, arguments); + }, + + // ------------------------------------------------------------------------- + // Public + // ------------------------------------------------------------------------- + /** + * @override + */ + autofocus: function () { + // force refresh label of button switch + this.$switchButton = this._renderSwitchButton(); + $('.o_switch_mode_button').replaceWith(this.$switchButton); + this._super.apply(this, arguments); + }, + /** + * @override + */ + renderButtons: function ($node) { + var $previewButton = $(qweb.render('website.ThemePreview.Buttons')); + $node.html($previewButton); + this.$switchButton = this._renderSwitchButton(); + $node.find('.o_switch_mode_button').replaceWith(this.$switchButton); + }, + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + /** + * Return jQuery Element button 'Switch Mode' with correct labelling. + * + * @private + */ + _renderSwitchButton: function () { + var isMobile = !!this.$('.is_mobile').length; + return $(qweb.render('website.ThemePreview.SwitchModeButton', { + 'icon': isMobile ? 'fa-desktop' : 'fa-refresh', + 'PreviewType': isMobile ? _t('Desktop') : _t('Mobile'), + })); + }, + // ------------------------------------------------------------------------- + // Handlers + // ------------------------------------------------------------------------- + /** + * Handler called when user click on 'Desktop/Mobile preview' button in forw view. + * + * @private + */ + _onSwitchButtonClick: function () { + this.$('.o_preview_frame').toggleClass('is_mobile'); + const $switchButton = this.$switchButton; + this.$switchButton = this._renderSwitchButton(); + $switchButton.replaceWith(this.$switchButton); + }, + /** + * Handler called when user click on 'Choose another theme' button in forw view. + * + * @private + */ + _onSwitchThemeClick: function () { + this.trigger_up('history_back'); + }, + /** + * Handler called when user click on 'Use this theme' button in forw view. + * + * @private + */ + _onUseThemeClick: function () { + const $loader = $(qweb.render('website.ThemePreview.Loader')); + $('body').append($loader); + return this._rpc({ + model: 'ir.module.module', + method: 'button_choose_theme', + args: [this.getSelectedIds()[0]], + context: this.initialState.context, + }, {shadow: true}) + .then(result => this.do_action(result)) + .guardedCatch(() => $loader.remove()); + }, +}); + +var ThemePreviewFormView = FormView.extend({ + config: _.extend({}, FormView.prototype.config, { + Controller: ThemePreviewController + }), +}); + +viewRegistry.add('theme_preview_form', ThemePreviewFormView); + +}); diff --git a/addons/website/static/src/js/theme_preview_kanban.js b/addons/website/static/src/js/theme_preview_kanban.js new file mode 100644 index 0000000000000000000000000000000000000000..570c75fba02ffd29d53805181adbe35f7e531671 --- /dev/null +++ b/addons/website/static/src/js/theme_preview_kanban.js @@ -0,0 +1,43 @@ +odoo.define('website.theme_preview_kanban', function (require) { +"use strict"; + +var KanbanController = require('web.KanbanController'); +var KanbanView = require('web.KanbanView'); +var ViewRegistry = require('web.view_registry'); + +var ThemePreviewKanbanController = KanbanController.extend({ + /** + * @override + */ + start: function () { + this.$el.addClass('o_view_kanban_theme_preview_controller'); + return this._super.apply(this, arguments); + }, +}); + +var ThemePreviewKanbanView = KanbanView.extend({ + searchMenuTypes: [], + + config: _.extend({}, KanbanView.prototype.config, { + Controller: ThemePreviewKanbanController, + }), + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + /** + * @override + * + */ + _createControlPanel: function (parent) { + return this._super.apply(this, arguments).then(controlPanel => { + var websiteLink = '<a class="btn btn-secondary ml-3" href="/"><i class="fa fa-close"></i></a>'; + controlPanel.$('div.o_cp_searchview').after(websiteLink); + return controlPanel; + }); + }, +}); + +ViewRegistry.add('theme_preview_kanban', ThemePreviewKanbanView); + +}); diff --git a/addons/website/static/src/js/widget_iframe.js b/addons/website/static/src/js/widget_iframe.js new file mode 100644 index 0000000000000000000000000000000000000000..8829ffa8684edfab9c7004ad69c00be16898dade --- /dev/null +++ b/addons/website/static/src/js/widget_iframe.js @@ -0,0 +1,28 @@ +odoo.define('website.iframe_widget', function (require) { +"use strict"; + + +var AbstractField = require('web.AbstractField'); +var core = require('web.core'); +var fieldRegistry = require('web.field_registry'); + +var QWeb = core.qweb; + +/** + * Display iframe + */ +var FieldIframePreview = AbstractField.extend({ + className: 'd-block o_field_iframe_preview m-0 h-100', + + _render: function () { + this.$el.html(QWeb.render('website.iframeWidget', { + url: this.value, + })); + }, +}); + +fieldRegistry.add('iframe', FieldIframePreview); + +return FieldIframePreview; + +}); diff --git a/addons/website/static/src/scss/website.backend.scss b/addons/website/static/src/scss/website.backend.scss index 0fd875c784331fb3a85fec32e3054939b0d5e7e6..bd22cb6b29753ff2228226558f01a12d126e5008 100644 --- a/addons/website/static/src/scss/website.backend.scss +++ b/addons/website/static/src/scss/website.backend.scss @@ -373,3 +373,76 @@ } } } + +.o_view_form_theme_preview_controller { + .o_control_panel > div:first-of-type { + display: none; + } + div.o_form_nosheet { + padding: 0px; + height:100%; + width:100%; + } + + .is_mobile { + @include media-breakpoint-up(md) { + iframe { + // mobile frame is rounded + border-radius: 15px; + height: 735px; + } + .img_mobile { + pointer-events: none; + display: block !important; + position: absolute; + top: 16px; + left: calc(50% - 200px) + } + .o_field_iframe_preview { + margin: auto !important; + padding: 53px 11px 58px 28px; + width: 416px; + } + } + } + +} +.o_view_kanban_theme_preview_controller { + .o_searchview_more, .o_control_panel > div:nth-child(2) { + display: none; + } +} + + +.o_theme_install_loader_container { + opacity: 0.85; + pointer-events: all; +} +.o_theme_install_loader { + display: inline-block; + position: relative; + width: 64px; + height: 64px; +} +.o_theme_install_loader:after { + content: " "; + display: block; + border-radius: 50%; + margin: 6px; + border: 26px solid $o-brand-primary; + border-color: $o-brand-primary transparent $o-brand-primary transparent; + animation: o_theme_install_loader 1.2s infinite; +} +@keyframes o_theme_install_loader { + 0% { + transform: rotate(0); + animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); + } + 50% { + transform: rotate(900deg); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + 100% { + transform: rotate(1800deg); + } +} diff --git a/addons/website/static/src/xml/theme_preview.xml b/addons/website/static/src/xml/theme_preview.xml new file mode 100644 index 0000000000000000000000000000000000000000..87ef87507d02ef9bea30b190dc1b956470935ca1 --- /dev/null +++ b/addons/website/static/src/xml/theme_preview.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<templates id="template" xml:space="preserve"> + <t t-name="website.ThemePreview.Buttons"> + <div> + <div class="o_form_buttons_view" role="toolbar" aria-label="Main actions"> + <button type="object" name="button_choose_theme" class="btn btn-primary o_use_theme"> + Start Now + </button> + <button class="btn btn-link o_switch_theme"> + Choose another theme + </button> + <button class="o_switch_mode_button"></button> + </div> + </div> + </t> + + <t t-name="website.ThemePreview.SwitchModeButton"> + <button class="o_switch_mode_button btn btn-light d-none d-md-inline"> + <i t-attf-class="fa #{icon or 'fa-refresh'} mr-1"></i> + <t t-esc="PreviewType"/> Preview + </button> + </t> + <t t-name="website.ThemePreview.Loader"> + <div class="o_theme_install_loader_container position-fixed fixed-top fixed-left h-100 w-100 bg-white d-flex justify-content-center align-items-center"> + <div class="o_theme_install_loader"></div> + </div> + </t> +</templates> diff --git a/addons/website/static/src/xml/website_widget.xml b/addons/website/static/src/xml/website_widget.xml new file mode 100644 index 0000000000000000000000000000000000000000..48a6d7c5503c4a5353062dab5f18c92150d9fbda --- /dev/null +++ b/addons/website/static/src/xml/website_widget.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<templates xml:space="preserve"> + + <t t-name="website.iframeWidget"> + <iframe + t-if="url" + height="100%" + width="100%" + frameBorder="0" + t-att-src="url" + class='d-block' + ></iframe> + <div t-else="">No Url</div> + </t> + +</templates> diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 01aab69e7f5a6685d084468cb9469e82633990bf..259aa6bc9b6f6a58ed2f89a29b08489afab5a6cf 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -36,6 +36,9 @@ <script type="text/javascript" src="/website/static/src/js/backend/button.js"/> <script type="text/javascript" src="/website/static/src/js/backend/dashboard.js"/> <script type="text/javascript" src="/website/static/src/js/backend/res_config_settings.js"/> + <script type="text/javascript" src="/website/static/src/js/widget_iframe.js"/> + <script type="text/javascript" src="/website/static/src/js/theme_preview_kanban.js"/> + <script type="text/javascript" src="/website/static/src/js/theme_preview_form.js"/> </xpath> </template> diff --git a/addons/website/views/website_views.xml b/addons/website/views/website_views.xml index 6fe26f86379bde75866f187334999355b8bca1ba..51fefb5bfb4eb955c3fdfb9b957f6c660b7f3c4d 100644 --- a/addons/website/views/website_views.xml +++ b/addons/website/views/website_views.xml @@ -289,7 +289,7 @@ <field name="name">Themes Kanban</field> <field name="model">ir.module.module</field> <field name="arch" type="xml"> - <kanban create="false" class="o_theme_kanban" default_order="state,sequence,name"> + <kanban create="false" class="o_theme_kanban" default_order="state,sequence,name" js_class="theme_preview_kanban"> <field name="icon"/> <field name="name"/> <field name="state"/> @@ -314,7 +314,7 @@ <div t-else="" class="o_button_area"> <button type="object" name="button_choose_theme" class="btn btn-primary">Use this theme</button> <hr t-if="record.url.value"/> - <a role="button" t-if="record.url.value" class="btn btn-secondary" t-att-href="record.url.value" target="_blank">Live Preview</a> + <button role="button" type="edit" t-if="record.url.value" class="btn btn-secondary">Live Preview</button> </div> <i states="installed" t-if="record.is_installed_on_current_website.raw_value" class="fa fa-check position-absolute p-1 m-2 rounded-circle bg-primary shadow" @@ -357,18 +357,6 @@ </record> <!-- Actions to list themes with custom kanban (launched on module installation) --> - <record id="theme_install_kanban_action" model="ir.actions.act_window"> - <field name="name">Choose a theme for your website</field> - <field name="res_model">ir.module.module</field> - <field name="view_mode">kanban</field> - <field name="view_id" ref="theme_view_kanban"/> - <field name="search_view_id" ref="theme_view_search"/> - <field name="domain" eval="[ - ('category_id', 'not in', [ref('base.module_category_hidden', False), ref('base.module_category_theme_hidden', False)]), - '|', ('category_id', '=', ref('base.module_category_theme', False)), ('category_id.parent_id', '=', ref('base.module_category_theme', False)) - ]"/> - </record> - <record id="theme_install_todo_action" model="ir.actions.server"> <field name="name">Config: Choose Your Theme</field> <field name="model_id" ref="model_ir_module_module"/> @@ -387,6 +375,38 @@ action = { <field name="sequence">0</field> </record> + <record id="theme_view_form_preview" model="ir.ui.view"> + <field name="name">website.form</field> + <field name="model">ir.module.module</field> + <field name="mode">primary</field> + <field name="arch" type="xml"> + <form create="false" edit="false" delete="0" js_class="theme_preview_form"> + <div class="o_preview_frame h-100"> + <field name='url' widget='iframe'/> + <img alt='' class='img_mobile' style='display:none' src="/website/static/src/img/phone.png"/> + </div> + + </form> + </field> + </record> + + <record id="theme_install_kanban_action" model="ir.actions.act_window"> + <field name="name">Choose a theme for your website</field> + <field name="res_model">ir.module.module</field> + <field name="view_mode">kanban,form</field> + <field name="view_id" ref="website.theme_view_kanban" /> + <field name="target">fullscreen</field> + <field name="view_ids" + eval="[(5, 0, 0), + (0, 0, {'view_mode': 'kanban', 'view_id': ref('website.theme_view_kanban')}), + (0, 0, {'view_mode': 'form', 'view_id': ref('website.theme_view_form_preview')})]"/> + <field name="search_view_id" ref="theme_view_search"/> + <field name="domain" eval="[ + ('category_id', 'not in', [ref('base.module_category_hidden', False), ref('base.module_category_theme_hidden', False)]), + '|', ('category_id', '=', ref('base.module_category_theme', False)), ('category_id.parent_id', '=', ref('base.module_category_theme', False)) + ]"/> + </record> + <!-- ONBOARDING --> <template id="onboarding_website_theme_step"> <t t-call="base.onboarding_step">