diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py index b73e8092e4f..7c3e9e1e33c 100644 --- a/addons/web/__manifest__.py +++ b/addons/web/__manifest__.py @@ -230,7 +230,6 @@ This module provides the core of the Odoo Web Client. 'web/static/src/legacy/scss/attachment_preview.scss', 'web/static/src/legacy/scss/base_document_layout.scss', 'web/static/src/legacy/scss/special_fields.scss', - 'web/static/src/legacy/scss/base_settings.scss', 'web/static/src/legacy/scss/fields_extra.scss', 'web/static/src/legacy/scss/form_view_extra.scss', 'web/static/src/legacy/scss/list_view_extra.scss', diff --git a/addons/web/static/src/legacy/scss/base_settings.scss b/addons/web/static/src/legacy/scss/base_settings.scss deleted file mode 100644 index 090297d5650..00000000000 --- a/addons/web/static/src/legacy/scss/base_settings.scss +++ /dev/null @@ -1,167 +0,0 @@ -// MIXINS -@mixin o-base-settings-horizontal-padding($padding-base: $input-btn-padding-y-sm) { - padding: $padding-base $o-horizontal-padding; - - @include media-breakpoint-up(xl) { - padding-left: $o-horizontal-padding*2;; - } -} - -// Use a very specif selector to overwrite generic form-view rules -.o_form_view.o_form_nosheet.o_base_settings { - display: flex; - flex-flow: column nowrap; - padding: 0px; -} - -// BASE SETTINGS LAYOUT -.o_base_settings { - height: 100%; - - .o_control_panel { - flex: 0 0 auto; - - .o_panel { - display: flex; - flex-flow: row wrap; - width: 100%; - - .o_settings_title, .o_setting_search { - flex: 1 0 auto; - width: map-get($o-extra-grid-breakpoints, vsm) / 2; - } - - .o_setting_search { - position: relative; - - .searchInput { - border: 0px; - max-width: 400px; - border-bottom: 1px solid $border-color; - box-shadow: none; - } - - .searchIcon { - @include o-position-absolute($right: 0); - color: map-get($grays, '700'); - } - - &:focus-within { - .searchInput { - border-bottom-color: $primary; - } - } - } - } - - .o_form_statusbar { - padding: 0; - margin: 0; - border: 0; - } - } - - .o_setting_container { - display: flex; - flex: 1 1 auto; - - overflow: auto; - - .settings_tab { - display: flex; - flex: 0 0 auto; - flex-flow: column nowrap; - background: map-get($grays, '900'); - overflow: auto; - - .selected { - background-color: map-get($grays, '800'); - box-shadow: inset 2px 0 0 $o-brand-primary; - } - - .tab { - display: flex; - padding: 0 $o-horizontal-padding*2 0 $o-horizontal-padding; - height: 40px; - color: map-get($grays, '400'); - font-size: 13px; - line-height: 40px; - cursor: pointer; - white-space: nowrap; - - .icon { - width: 23px; - min-width: 23px; - margin-right: 10px; - } - - &:hover, &.selected { - color: white; - } - } - } - - .settings { - position: relative; - flex: 1 1 100%; - background-color: $o-view-background-color; - overflow: auto; - - > .app_settings_block { - h2 { - margin: 0 0 !important; - @include o-base-settings-horizontal-padding(.4rem); - background-color: map-get($grays, '200'); - font-size: 15px; - - &.o_invisible_modifier + .o_settings_container { - display: none; - } - } - - h3 { - margin: 0 0 !important; - @include o-base-settings-horizontal-padding(.4rem); - font-weight: 400; - font-size: 13px; - } - - .o_settings_container { - max-width: map-get($grid-breakpoints, lg); // Provide a maximum container size to ensure readability - @include o-base-settings-horizontal-padding(0); - margin-bottom: 24px; - } - } - - .settingSearchHeader { - display: flex; - align-items: center; - margin-bottom: 10px; - @include o-base-settings-horizontal-padding(.8rem); - background-color: map-get($grays, '200'); - - .icon { - width: 1.4em; - height: 1.4em; - margin-right: 10px; - } - - & + .app_settings_header { - margin-top: -10px; - } - } - - .app_settings_header { - @include o-base-settings-horizontal-padding(0); - } - - .highlighter { - background: yellow; - } - } - - .d-block { - display: block!important; - } - } -} diff --git a/addons/web/static/src/webclient/settings_form_view/settings/settings_page.js b/addons/web/static/src/webclient/settings_form_view/settings/settings_page.js index 54d1da58f02..b69069473b0 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings/settings_page.js +++ b/addons/web/static/src/webclient/settings_form_view/settings/settings_page.js @@ -1,4 +1,5 @@ /** @odoo-module **/ +import { ActionSwiper } from "@web/core/action_swiper/action_swiper"; const { Component, useState } = owl; @@ -14,9 +15,35 @@ export class SettingsPage extends Component { } } + getCurrentIndex() { + return this.props.modules.findIndex((object) => { + return object.key === this.state.selectedTab; + }); + } + + hasRightSwipe() { + return ( + this.env.isSmall && this.state.search.value.length === 0 && this.getCurrentIndex() !== 0 + ); + } + hasLeftSwipe() { + return ( + this.env.isSmall && + this.state.search.value.length === 0 && + this.getCurrentIndex() !== this.props.modules.length - 1 + ); + } + onRightSwipe() { + this.state.selectedTab = this.props.modules[this.getCurrentIndex() - 1].key; + } + onLeftSwipe() { + this.state.selectedTab = this.props.modules[this.getCurrentIndex() + 1].key; + } + onSettingTabClick(key) { this.state.selectedTab = key; this.env.searchState.value = ""; } } SettingsPage.template = "web.SettingsPage"; +SettingsPage.components = { ActionSwiper }; diff --git a/addons/web/static/src/webclient/settings_form_view/settings/settings_page.xml b/addons/web/static/src/webclient/settings_form_view/settings/settings_page.xml index 67f61fe75a7..26584c4ef0c 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings/settings_page.xml +++ b/addons/web/static/src/webclient/settings_form_view/settings/settings_page.xml @@ -1,16 +1,27 @@ -
+
-
- - -
+ +
+ + +
+
diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_view.scss b/addons/web/static/src/webclient/settings_form_view/settings_form_view.scss index 9e4f6fbd071..d9360b2c8ac 100644 --- a/addons/web/static/src/webclient/settings_form_view/settings_form_view.scss +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_view.scss @@ -58,3 +58,171 @@ top: 0px; right: 40px; } + +// MIXINS +@mixin o-base-settings-horizontal-padding($padding-base: $input-btn-padding-y-sm) { + padding: $padding-base $o-horizontal-padding; + + @include media-breakpoint-up(xl) { + padding-left: $o-horizontal-padding*2;; + } +} + +// Use a very specif selector to overwrite generic form-view rules +.o_form_view.o_form_nosheet.o_base_settings { + display: flex; + flex-flow: column nowrap; + padding: 0px; +} + +// BASE SETTINGS LAYOUT +.o_base_settings { + height: 100%; + + .o_control_panel { + flex: 0 0 auto; + + .o_panel { + display: flex; + flex-flow: row wrap; + width: 100%; + + .o_settings_title, .o_setting_search { + flex: 1 0 auto; + width: map-get($o-extra-grid-breakpoints, vsm) / 2; + } + + .o_setting_search { + position: relative; + + .searchInput { + border: 0px; + max-width: 400px; + border-bottom: 1px solid $border-color; + box-shadow: none; + } + + .searchIcon { + @include o-position-absolute($right: 0); + color: map-get($grays, '700'); + } + + &:focus-within { + .searchInput { + border-bottom-color: $primary; + } + } + } + } + + .o_form_statusbar { + padding: 0; + margin: 0; + border: 0; + } + } + + .o_setting_container { + display: flex; + flex: 1 1 auto; + + overflow: auto; + + .settings_tab { + display: flex; + flex: 0 0 auto; + flex-flow: column nowrap; + background: map-get($grays, '900'); + overflow: auto; + + .selected { + background-color: map-get($grays, '800'); + box-shadow: inset 2px 0 0 $o-brand-primary; + } + + .tab { + display: flex; + padding: 0 $o-horizontal-padding*2 0 $o-horizontal-padding; + height: 40px; + color: map-get($grays, '400'); + font-size: 13px; + line-height: 40px; + cursor: pointer; + white-space: nowrap; + + .icon { + width: 23px; + min-width: 23px; + margin-right: 10px; + } + + &:hover, &.selected { + color: white; + } + } + } + + .settings { + position: relative; + flex: 1 1 100%; + background-color: $o-view-background-color; + overflow: auto; + + > .app_settings_block { + h2 { + margin: 0 0 !important; + @include o-base-settings-horizontal-padding(.4rem); + background-color: map-get($grays, '200'); + font-size: 15px; + + &.o_invisible_modifier + .o_settings_container { + display: none; + } + } + + h3 { + margin: 0 0 !important; + @include o-base-settings-horizontal-padding(.4rem); + font-weight: 400; + font-size: 13px; + } + + .o_settings_container { + max-width: map-get($grid-breakpoints, lg); // Provide a maximum container size to ensure readability + @include o-base-settings-horizontal-padding(0); + margin-bottom: 24px; + } + } + + .settingSearchHeader { + display: flex; + align-items: center; + margin-bottom: 10px; + @include o-base-settings-horizontal-padding(.8rem); + background-color: map-get($grays, '200'); + + .icon { + width: 1.4em; + height: 1.4em; + margin-right: 10px; + } + + & + .app_settings_header { + margin-top: -10px; + } + } + + .app_settings_header { + @include o-base-settings-horizontal-padding(0); + } + + .highlighter { + background: yellow; + } + } + + .d-block { + display: block!important; + } + } +} diff --git a/addons/web/static/src/webclient/settings_form_view/settings_form_view_mobile.scss b/addons/web/static/src/webclient/settings_form_view/settings_form_view_mobile.scss new file mode 100644 index 00000000000..5b2b7b4ad7b --- /dev/null +++ b/addons/web/static/src/webclient/settings_form_view/settings_form_view_mobile.scss @@ -0,0 +1,68 @@ +@include media-breakpoint-down(md) { + + .o_base_settings { + flex-flow: column nowrap; + + > .o_control_panel { + @include o-webclient-padding($top: 10px, $bottom: 10px); + } + + .o_setting_container { + flex-flow: column nowrap; + + .settings_tab { + flex: 0 0 $o-base-settings-mobile-tabs-height; + flex-direction: row; + justify-content: space-between; + position: relative; + overflow: hidden; + overflow-x: auto; + padding: 0; + background: #5E5E5E; + + .tab { + display: block; + width: auto; + height: $o-base-settings-mobile-tabs-height; + padding: $o-base-settings-mobile-tabs-height*0.25 $o-base-settings-mobile-tabs-height*0.4; + text-align: center; + font-size: 14px; + font-weight: 500; + line-height: inherit; + transition: 0.2s all ease 0s; + transform: translate3d(0, 0, 0); + + .app_name { + display: block; + white-space: nowrap; + text-transform: uppercase; + } + + &:after { + content: ''; + background: $o-brand-primary; + opacity: 0; + @include o-position-absolute(auto, 0, 0, 0); + width: 100%; + height: 3px; + transition: 0.2s all ease 0s; + } + + &.current { + color: white; + font-weight: bold; + + // Reset default style for 'selected' tabs + box-shadow: none; + background: none; + + &:after { + opacity: 1; + } + } + } + } + } + + } +} \ No newline at end of file diff --git a/addons/web/static/tests/mobile/settings_form_view_mobile_tests.js b/addons/web/static/tests/mobile/settings_form_view_mobile_tests.js new file mode 100644 index 00000000000..92a561a13e7 --- /dev/null +++ b/addons/web/static/tests/mobile/settings_form_view_mobile_tests.js @@ -0,0 +1,172 @@ +/** @odoo-module **/ + +import { getFixture, mockTimeout, nextTick } from "@web/../tests/helpers/utils"; +import { makeView, setupViewRegistries } from "@web/../tests/views/helpers"; +import { swipeLeft, swipeRight } from "@web/../tests/mobile/helpers"; +import { registry } from "@web/core/registry"; + +const { EventBus } = owl; + +let serverData, target; + +const serviceRegistry = registry.category("services"); + +QUnit.module("Mobile SettingsFormView", (hooks) => { + hooks.beforeEach(() => { + serverData = { + models: { + project: { + fields: { + foo: { string: "Foo", type: "boolean" }, + bar: { string: "Bar", type: "boolean" }, + }, + }, + }, + }; + target = getFixture(); + setupViewRegistries(); + }); + + QUnit.module("BaseSettings Mobile"); + + QUnit.test("swipe settings in mobile [REQUIRE TOUCHEVENT]", async function (assert) { + const { execRegisteredTimeouts } = mockTimeout(); + serviceRegistry.add("ui", { + start(env) { + Object.defineProperty(env, "isSmall", { + value: true, + }); + return { + bus: new EventBus(), + size: 0, + isSmall: true, + }; + }, + }); + await makeView({ + type: "form", + resModel: "project", + serverData, + arch: ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
`, + }); + + await swipeLeft(target, ".settings"); + execRegisteredTimeouts(); + await nextTick(); + assert.hasAttrValue( + target.querySelector(".selected"), + "data-key", + "project", + "current setting should be project" + ); + + await swipeRight(target, ".settings"); + execRegisteredTimeouts(); + await nextTick(); + assert.hasAttrValue( + target.querySelector(".selected"), + "data-key", + "crm", + "current setting should be crm" + ); + }); + + QUnit.test( + "swipe settings on larger screen sizes has no effect [REQUIRE TOUCHEVENT]", + async function (assert) { + const { execRegisteredTimeouts } = mockTimeout(); + serviceRegistry.add("ui", { + start(env) { + Object.defineProperty(env, "isSmall", { + value: false, + }); + return { + bus: new EventBus(), + size: 9, + isSmall: false, + }; + }, + }); + await makeView({ + type: "form", + resModel: "project", + serverData, + arch: ` +
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
`, + }); + + await swipeLeft(target, ".settings"); + execRegisteredTimeouts(); + await nextTick(); + assert.hasAttrValue( + target.querySelector(".selected"), + "data-key", + "crm", + "current setting should still be crm" + ); + } + ); +});