[MIG] web_responsive: Migration to 14.0

pull/1819/head
Sergey Shebanin 2021-02-11 15:07:56 +03:00 committed by Splash
parent f46fa14d1c
commit 5036a68a01
24 changed files with 1480 additions and 670 deletions

View File

@ -0,0 +1 @@
../../../../web_responsive

View File

@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)

View File

@ -14,16 +14,16 @@ Web Responsive
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/13.0/web_responsive
:target: https://github.com/OCA/web/tree/14.0/web_responsive
:alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_responsive
:target: https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_responsive
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/13.0
:target: https://runbot.odoo-community.org/runbot/162/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
|badge1| |badge2| |badge3| |badge4| |badge5|
This module adds responsiveness to web backend.
@ -142,9 +142,10 @@ Known issues / Roadmap
device screen size. This means that, if you change the size of your browser,
you should reload the web client to get the full experience for that
new size. This is Odoo's own limitation.
* Kanban mobile/desktop mode switch on screen resize. F.x. when rotating a device
to landscape orientation.
* App navigation with keyboard.
* Handle long titles on forms in a better way
* Standard sticky headers seems to not work properly on iOS Safari/Chrome (see #1626).
Bug Tracker
===========
@ -152,7 +153,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
`feedback <https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
@ -175,6 +176,7 @@ Contributors
* Alexandre Díaz <dev@redneboa.es>
* Mathias Markl <mathias.markl@mukit.at>
* Iván Todorovich <ivan.todorovich@gmail.com>
* Sergey Shebanin <sergey@shebanin.ru>
Maintainers
~~~~~~~~~~~
@ -198,8 +200,8 @@ promote its widespread use.
Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Yajo| |maintainer-Tardo|
|maintainer-Yajo| |maintainer-Tardo|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/13.0/web_responsive>`_ project on GitHub.
This module is part of the `OCA/web <https://github.com/OCA/web/tree/14.0/web_responsive>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -6,7 +6,7 @@
{
"name": "Web Responsive",
"summary": "Responsive web client, community-supported",
"version": "13.0.2.6.4",
"version": "14.0.1.0.0",
"category": "Website",
"website": "https://github.com/OCA/web",
"author": "LasLabs, Tecnativa, " "Odoo Community Association (OCA)",
@ -18,10 +18,13 @@
"data": ["views/assets.xml", "views/res_users.xml", "views/web.xml"],
"qweb": [
"static/src/xml/apps.xml",
"static/src/xml/form_view.xml",
"static/src/xml/form_buttons.xml",
"static/src/xml/menu.xml",
"static/src/xml/navbar.xml",
"static/src/xml/document_viewer.xml",
"static/src/xml/attachment_viewer.xml",
"static/src/xml/discuss.xml",
"static/src/xml/control_panel.xml",
"static/src/xml/search_panel.xml",
],
"sequence": 1,
}

View File

@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 13.0\n"
"Project-Id-Version: Odoo Server 14.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: \n"
"Language-Team: \n"
@ -15,9 +15,16 @@ msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "#menu_id=#{app.menuID}&action_id=#{app.actionID}"
msgid "All"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "CLEAR"
msgstr ""
#. module: web_responsive
@ -27,44 +34,47 @@ msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/document_viewer.xml:0
#, python-format
msgid "Close"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Create"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Discard"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Edit"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/document_viewer.xml:0
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "FILTER"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Maximize"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/document_viewer.xml:0
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#: code:addons/web_responsive/static/src/xml/attachment_viewer.xml:0
#, python-format
msgid "Minimize"
msgstr ""
@ -76,15 +86,23 @@ msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Quick actions"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/form_view.xml:0
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/search_panel.xml:0
#, python-format
msgid "SEE RESULT"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Save"
msgstr ""
@ -96,6 +114,14 @@ msgstr ""
msgid "Search menus..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid "Search..."
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/menu.xml:0
@ -108,6 +134,20 @@ msgstr ""
msgid "Sided"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/form_buttons.xml:0
#, python-format
msgid "Today"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/js/kanban_renderer_mobile.js:0
#, python-format
msgid "Undefined"
msgstr ""
#. module: web_responsive
#: model:ir.model,name:web_responsive.model_res_users
msgid "Users"
@ -115,24 +155,7 @@ msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/discuss.xml:0
#: code:addons/web_responsive/static/src/xml/control_panel.xml:0
#, python-format
msgid ""
"btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-none"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/apps.xml:0
#: code:addons/web_responsive/static/src/xml/document_viewer.xml:0
#, python-format
msgid "false"
msgstr ""
#. module: web_responsive
#. openerp-web
#: code:addons/web_responsive/static/src/xml/document_viewer.xml:0
#, python-format
msgid ""
"modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer"
msgid "View switcher"
msgstr ""

View File

@ -14,11 +14,11 @@ class ResUsers(models.Model):
)
def __init__(self, pool, cr):
""" Override of __init__ to add access rights.
"""Override of __init__ to add access rights.
Access rights are disabled by default, but allowed on some specific
fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
"""
super(ResUsers, self).__init__(pool, cr)
super().__init__(pool, cr)
# duplicate list to avoid modifying the original reference
type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
type(self).SELF_WRITEABLE_FIELDS.extend(["chatter_position"])

View File

@ -5,3 +5,4 @@
* Alexandre Díaz <dev@redneboa.es>
* Mathias Markl <mathias.markl@mukit.at>
* Iván Todorovich <ivan.todorovich@gmail.com>
* Sergey Shebanin <sergey@shebanin.ru>

View File

@ -367,7 +367,7 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_responsive"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-13-0/web-13-0-web_responsive"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/13.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Production/Stable" src="https://img.shields.io/badge/maturity-Production%2FStable-green.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/lgpl-3.0-standalone.html"><img alt="License: LGPL-3" src="https://img.shields.io/badge/licence-LGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/web/tree/14.0/web_responsive"><img alt="OCA/web" src="https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/web-14-0/web-14-0-web_responsive"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/162/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>This module adds responsiveness to web backend.</p>
<p>Features for all devices:</p>
<ul>
@ -485,7 +485,7 @@ new size. This is Odoos own limitation.</li>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smashing it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2013.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<a class="reference external" href="https://github.com/OCA/web/issues/new?body=module:%20web_responsive%0Aversion:%2014.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
@ -507,6 +507,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<li>Alexandre Díaz &lt;<a class="reference external" href="mailto:dev&#64;redneboa.es">dev&#64;redneboa.es</a>&gt;</li>
<li>Mathias Markl &lt;<a class="reference external" href="mailto:mathias.markl&#64;mukit.at">mathias.markl&#64;mukit.at</a>&gt;</li>
<li>Iván Todorovich &lt;<a class="reference external" href="mailto:ivan.todorovich&#64;gmail.com">ivan.todorovich&#64;gmail.com</a>&gt;</li>
<li>Sergey Shebanin &lt;<a class="reference external" href="mailto:sergey&#64;shebanin.ru">sergey&#64;shebanin.ru</a>&gt;</li>
</ul>
</div>
<div class="section" id="maintainers">
@ -518,7 +519,7 @@ mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</p>
<p><a class="reference external" href="https://github.com/Yajo"><img alt="Yajo" src="https://github.com/Yajo.png?size=40px" /></a> <a class="reference external" href="https://github.com/Tardo"><img alt="Tardo" src="https://github.com/Tardo.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/13.0/web_responsive">OCA/web</a> project on GitHub.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/14.0/web_responsive">OCA/web</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>

View File

@ -0,0 +1,97 @@
@include media-breakpoint-down(sm) {
.o_kanban_view.o_kanban_grouped {
display: block;
position: relative;
overflow-x: hidden;
&.o_renderer_with_searchpanel {
width: 100%;
}
.o_kanban_mobile_tabs_container {
position: sticky;
display: flex;
justify-content: space-between;
width: 100%;
top: 0;
z-index: 1;
background-color: #5e5e5e;
.o_kanban_mobile_add_column {
height: $o-kanban-mobile-tabs-height;
padding: 10px;
border-left: grey 1px solid;
color: white;
font-size: 14px;
}
.o_kanban_mobile_tabs {
position: relative;
display: flex;
width: 100%;
height: $o-kanban-mobile-tabs-height;
overflow-x: auto;
.o_kanban_mobile_tab {
height: $o-kanban-mobile-tabs-height;
padding: 10px 20px;
font-size: 14px;
color: white;
&.o_current {
font-weight: bold;
border-bottom: 3px solid $o-brand-primary;
}
.o_column_title {
white-space: nowrap;
text-transform: uppercase;
}
}
}
}
.o_kanban_columns_content {
position: relative;
}
// [class] to get same specificity as elsewhere (kanban_view.less)
&[class] .o_kanban_group:not(.o_column_folded) {
@include o-position-absolute(
$top: $o-kanban-mobile-tabs-height,
$left: 0,
$bottom: 0
);
width: 100%;
padding: 0;
margin-left: 0; // override the margin-left: -1px of the desktop mode
border: none;
&.o_current {
position: inherit;
top: 0;
&.o_kanban_no_records {
// set a default height for clarity when embedded in another view
min-height: $o-kanban-mobile-empty-height;
}
}
.o_kanban_header {
display: none;
}
.o_kanban_record,
.o_kanban_quick_create {
border: none;
border-bottom: 1px solid lightgray;
padding: 10px 16px;
margin: 0;
}
}
}
.o_kanban_view .o_column_quick_create {
.o_quick_create_folded {
display: none !important;
}
.o_quick_create_unfolded {
width: 100%;
}
}
}

View File

@ -0,0 +1,109 @@
.o_web_client {
.o_mobile_search {
position: fixed;
top: 0;
left: 0;
bottom: 0;
padding: 0;
width: 100%;
background-color: white;
z-index: $zindex-modal;
overflow: auto;
.o_mobile_search_header {
height: 46px;
margin-bottom: 10px;
width: 100%;
background-color: $o-brand-odoo;
color: white;
span:active {
background-color: darken($o-brand-primary, 10%);
}
span {
cursor: pointer;
}
}
.o_searchview_input_container {
display: flex;
padding: 15px 20px 0 20px;
position: relative;
.o_searchview_input {
width: 100%;
margin-bottom: 15px;
border-bottom: 1px solid $o-brand-secondary;
}
.o_searchview_facet {
border-radius: 10px;
display: inline-flex;
order: 1;
.o_searchview_facet_label {
border-radius: 2em 0em 0em 2em;
}
}
.o_searchview_autocomplete {
top: 100%;
> li {
margin: 5px 0px;
}
}
}
.o_mobile_search_filter {
padding-bottom: 15%;
.o_dropdown {
width: 100%;
margin: 15px 5px 0px 5px;
border: solid 1px darken(gray("200"), 20%);
}
.o_dropdown_toggler_btn {
width: 100%;
text-align: left;
&:after {
display: none;
}
}
// We disable the backdrop in this case because it prevents any
// interaction outside of a dropdown while it is open.
.dropdown-backdrop {
z-index: -1;
}
.dropdown-menu {
// Here we use !important because of popper js adding custom style
// to element so to override it use !important
position: relative !important;
width: 100% !important;
transform: translate3d(0, 0, 0) !important;
box-shadow: none;
border: none;
color: gray("600");
.divider {
margin: 0px;
}
> li > a {
padding: 10px 26px;
}
}
}
.o_mobile_search_show_result {
padding: 10px;
font-size: 15px;
}
}
}
// Search panel
@include media-breakpoint-down(sm) {
.o_controller_with_searchpanel {
display: block;
.o_search_panel {
height: auto;
padding: 8px;
border-left: 1px solid $gray-300;
section {
padding: 0px 16px;
}
}
.o_search_panel_summary {
cursor: pointer;
}
}
}

View File

@ -328,38 +328,114 @@ html .o_web_client .o_action_manager .o_action {
max-width: 100%;
}
// Control panel (breadcrumbs, search box, buttons...)
@include media-breakpoint-down(sm) {
.o_control_panel {
// Arrange buttons to use space better
.breadcrumb,
.o_cp_buttons,
.o_cp_left,
.o_cp_right,
.o_cp_searchview {
flex: 1 1 100%;
@include media-breakpoint-up(md) {
flex-basis: 50%;
// Make enough space for search panel filters buttons
.o_control_panel {
// There is no media breakpoint for XL upper bound
@include media-breakpoint-up(lg) {
@media (max-width: 1360px) {
.o_cp_top_left,
.o_cp_bottom_left {
width: 40%;
}
.o_cp_top_right,
.o_cp_bottom_right {
width: 60%;
}
}
}
// For FULL HD devices
@media (min-width: 1900px) {
.o_cp_top_left,
.o_cp_bottom_left {
width: 60%;
}
.o_cp_top_right,
.o_cp_bottom_right {
width: 40%;
}
}
@include media-breakpoint-only(md) {
.o_search_options_hide_labels .o_dropdown_title {
display: none;
}
}
.o_cp_bottom_right {
height: 30px;
}
}
.breadcrumb {
// Mobile Control panel (breadcrumbs, search box, buttons...)
@include media-breakpoint-down(sm) {
.o_control_panel {
// Avoid horizontal scrolling of control panel.
// It doesn't work on iOS Safari, but it looks similar as
// without this patch. With this patch it looks better for
// other browsers.
position: sticky;
left: 0;
z-index: 3;
// Arrange buttons to use space better
.o_cp_top_left,
.o_cp_top_right {
flex: 1 1 100%;
}
.o_cp_top_left {
flex-basis: 80%;
}
.o_cp_searchview,
.o_cp_right {
flex-basis: 10%;
.o_cp_top_right {
flex-basis: 20%;
}
.o_cp_left {
flex-basis: 50%;
white-space: nowrap;
.o_cp_bottom {
position: relative; // Necessary for dropdown menu positioning
display: block;
margin: 0;
}
.o_cp_bottom_left {
float: left;
margin: 5px 0;
}
.o_cp_bottom_right {
float: right;
height: 30px;
padding-left: 10px;
margin: 5px 0;
}
.o_cp_bottom_right,
.o_cp_pager {
white-space: nowrap;
}
.o_cp_pager {
margin-bottom: 0;
}
.o_cp_bottom_left > .o_cp_action_menus {
padding-right: 0;
.o_dropdown_title,
.fa-chevron-right,
.fa-chevron-down {
display: none;
}
.o_dropdown_toggler_btn {
margin: 0px 2px;
}
@include media-breakpoint-down(xs) {
.o_dropdown {
position: static;
}
.dropdown-menu {
right: 0;
left: 0;
top: 35px;
}
}
}
// Hide all but 2 last breadcrumbs, and render 2nd-to-last as arrow
.breadcrumb-item {
@ -379,7 +455,7 @@ html .o_web_client .o_action_manager .o_action {
&:nth-last-of-type(2) {
&::before {
color: var(--primary);
content: "\f048"; // .fa-step-backward
content: "\f060"; // .fa-arrow-left
cursor: pointer;
font-family: FontAwesome;
}
@ -396,36 +472,67 @@ html .o_web_client .o_action_manager .o_action {
text-overflow: ellipsis;
}
// Empty sidebar should not break layout
.o_cp_sidebar:blank {
display: none;
}
// In case you install `mail`, there is a mess on BS vs inline styles
// we need to fix
.o_cp_buttons .btn.d-block:not(.d-none) {
display: inline-block !important;
}
// Dropdown with buttons to switch the view type
.o_cp_switch_buttons.show {
.dropdown-menu {
align-content: center;
.o_searchview {
padding: 1px 0px 3px 0px;
&.o_searchview_mobile {
cursor: pointer;
}
&.o_searchview_quick {
display: flex;
flex-direction: row;
justify-content: space-around;
padding: 0;
.btn {
border: {
bottom: 0;
radius: 0;
top: 0;
}
flex: 1 1 auto;
align-items: center;
.o_searchview_input_container {
flex: 1 1 auto;
}
}
}
}
.o_calendar_view .o_calendar_widget {
.fc-timeGridDay-view .fc-axis,
.fc-timeGridWeek-view .fc-axis {
padding-left: 0px;
}
.fc-dayGridMonth-view {
padding-left: 0px;
.fc-week-number {
display: none;
}
}
.fc-dayGridYear-view {
padding-left: 0px;
> .fc-month-container > .fc-month {
width: 100%;
}
}
.fc-timeGridDay-view .fc-widget-header {
margin: 0 4px;
}
.fc-timeGridWeek-view .fc-widget-header {
word-spacing: 4em;
white-space: normal;
text-align: center;
}
}
.o_base_settings .o_setting_container {
display: block;
.settings_tab {
flex-flow: row nowrap;
padding-top: 0px;
.tab {
padding-right: 16px;
}
.selected {
background-color: #212529;
box-shadow: inset 0 -5px #7c7bad;
}
}
}
}
// Normal views
@ -440,13 +547,31 @@ html .o_web_client .o_action_manager .o_action {
overflow-x: auto;
}
.oe_chatter {
.o_FormRenderer_chatterContainer {
padding-top: 0;
.o_Activity_info {
flex-wrap: wrap;
}
.o_ActivityBox_title {
margin-bottom: 0;
}
.o_MessageList_separatorDate {
padding-bottom: 0;
}
}
.o_chatter_topbar {
height: auto;
flex-wrap: wrap-reverse;
// Sided chatter scrolling behavior
.o_Chatter {
height: fit-content;
.o_Chatter_fixedPanel {
position: sticky;
top: 0;
z-index: 1;
background-color: white;
padding-bottom: 10px;
}
.o_Chatter_scrollPanel {
overflow: initial;
}
}
// Sticky statusbar
@ -458,17 +583,14 @@ html .o_web_client .o_action_manager .o_action {
// Support for long title (with ellipsis)
.oe_title {
span.o_field_widget {
&:not(.oe_inline) {
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: initial;
&:active {
white-space: normal;
}
span.o_field_widget:not(.oe_inline) {
max-width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
width: initial;
&:active {
white-space: normal;
}
}
}
@ -520,7 +642,7 @@ html .o_web_client .o_action_manager .o_action {
}
height: 100%;
}
.o_statusbar_buttons > .btn {
.o_statusbar_buttons.dropdown-menu > .btn {
border-radius: 0;
border: 0;
width: 100%;
@ -541,7 +663,7 @@ html .o_web_client .o_action_manager .o_action {
// Full width in form sheets
.o_form_sheet,
.oe_chatter {
.o_FormRenderer_chatterContainer {
min-width: auto;
max-width: 98%;
}
@ -553,7 +675,7 @@ html .o_web_client .o_action_manager .o_action {
}
}
.o_chatter {
.o_FormRenderer_chatterContainer {
padding-top: initial;
// Display send button on small screens
@ -605,7 +727,7 @@ html .o_web_client .o_action_manager .o_action {
}
}
.o_chatter {
.o_FormRenderer_chatterContainer {
border-left: 1px solid gray("400");
flex: 0 0 $chatter_zone_width;
max-width: initial;
@ -645,7 +767,6 @@ html .o_web_client .o_action_manager .o_action {
.table-responsive {
.o_list_table {
// th & td are here for compatibility with chrome
thead,
thead tr:nth-child(1) th {
position: sticky;
top: 0;
@ -693,114 +814,105 @@ html .o_web_client .o_action_manager .o_action {
cursor: progress;
}
// Document Viewer
.o_web_client.o_chatter_position_sided {
.o_modal_fullscreen.o_document_viewer {
// On-top of navbar
z-index: 10;
&.o_responsive_document_viewer {
/* Show sided viewer on large screens */
@include media-breakpoint-up(lg) {
width: $chatter_zone_width;
margin-left: auto;
right: 0;
/* Show/Hide control buttons (next, prev, etc..) */
&:hover .arrow,
&:hover .o_viewer_toolbar {
display: flex;
}
.arrow,
.o_viewer_toolbar {
display: none;
}
.o_viewer_img_wrapper {
position: relative;
.o_viewer_pdf {
width: 95%;
}
}
}
.o_minimize_btn {
display: none;
}
// Attachment Viewer
.o_web_client.o_chatter_position_sided .o_Dialog_AttachmentViewer {
/* Show sided viewer on large screens */
@include media-breakpoint-up(lg) {
position: static;
.o_AttachmentViewer_main {
padding-bottom: 20px;
}
&:not(.o_responsive_document_viewer) {
.o_maximize_btn {
.o_AttachmentViewer {
// On-top of navbar
z-index: 10;
position: absolute;
right: 0;
top: 0;
bottom: 0;
margin-left: auto;
background-color: rgba(0, 0, 0, 0.7);
.o_AttachmentViewer_name {
display: contents;
}
width: $chatter_zone_width;
&.o_AttachmentViewer_maximized {
width: 100%;
}
/* Show/Hide control buttons (next, prev, etc..) */
&:hover .o_AttachmentViewer_buttonNavigation,
&:hover .o_AttachmentViewer_toolbar {
display: flex;
}
.o_AttachmentViewer_buttonNavigation,
.o_AttachmentViewer_toolbar {
display: none;
}
}
@include media-breakpoint-down(lg) {
.o_minimize_btn,
.o_maximize_btn {
display: none;
.o_AttachmentViewer_viewIframe {
width: 95%;
}
}
}
}
/* Max/Min buttons only are usefull in sided mode */
.o_web_client:not(.o_chatter_position_sided) {
.o_minimize_btn,
.o_maximize_btn {
display: none;
}
}
// Apply improvements for Document Viewer on all modes
.o_modal_fullscreen .o_viewer_content {
.o_viewer-header {
.o_image_caption {
display: contents;
}
// Now uses a container to have more buttons
.o_buttons {
min-width: 35px;
text-align: right;
// Now close button ('X') it's a fa-icon
> .o_close_btn {
top: unset;
left: unset;
bottom: unset;
right: unset;
font-size: unset;
font-weight: unset;
}
}
}
}
// Search Panel
@include media-breakpoint-down(sm) {
// Hide search panel
.o_controller_with_searchpanel {
.o_search_panel {
@include media-breakpoint-down(md) {
.o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize {
display: none;
}
}
}
/* Attachment Viewer Max/Min buttons only are useful in sided mode */
.o_web_client:not(.o_chatter_position_sided) {
.o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize {
display: none;
}
}
.o_control_panel {
// Filter Menu item
.o_filters_menu {
// Filter Menu
// Cut long filters names in the filters menu
.o_filter_menu {
.o_menu_item {
@include o-search-options-dropdown-custom-item;
width: auto;
@include media-breakpoint-up(md) {
max-width: 250px;
}
a {
overflow: hidden;
text-overflow: ellipsis;
}
}
}
// Enable scroll on dropdowns
.o_cp_buttons .dropdown-menu {
max-height: 70vh;
overflow-y: auto;
overflow-x: hidden;
}
// Dropdown with buttons to switch the view type
.o_cp_switch_buttons.show {
.dropdown-menu {
align-content: center;
display: flex;
flex-direction: row;
justify-content: space-around;
padding: 0;
.btn {
border: {
bottom: 0;
radius: 0;
top: 0;
}
font-size: 1.3em;
}
}
}
}
// Shortcut table ui improvement
.o_shortcut_table {
width: 100%;
}

View File

@ -1,300 +0,0 @@
/* Copyright Odoo S.A.
* Ported to 13.0 by Copyright 2020 Tecnativa - Alexandre Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive.Discuss", function(require) {
"use strict";
const config = require("web.config");
if (!config.device.isMobile) {
return;
}
const core = require("web.core");
const Discuss = require("mail.Discuss");
const QWeb = core.qweb;
Discuss.include({
contentTemplate: "mail.discuss_mobile",
events: Object.assign({}, Discuss.prototype.events, {
"click .o_mail_mobile_tab": "_onClickMobileTab",
"click .o_mailbox_inbox_item": "_onClickMobileMailboxItem",
"click .o_mail_preview": "_onClickMobileMailPreview",
}),
/**
* @override
*/
init: function() {
this._super.apply(this, arguments);
this._currentState = this._defaultThreadID;
},
/**
* @override
*/
start: function() {
this._$mainContent = this.$(".o_mail_discuss_content");
return this._super
.apply(this, arguments)
.then(this._updateControlPanel.bind(this));
},
/**
* @override
*/
on_attach_callback: function() {
if (this._thread && this._isInInboxTab()) {
this._threadWidget.scrollToPosition(
this._threadsScrolltop[this._thread.getID()]
);
}
},
/**
* @override
*/
on_detach_callback: function() {
if (this._isInInboxTab()) {
this._threadsScrolltop[
this._thread.getID()
] = this._threadWidget.getScrolltop();
}
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* @private
* @returns {Boolean} true iff we currently are in the Inbox tab
*/
_isInInboxTab: function() {
return _.contains(["mailbox_inbox", "mailbox_starred"], this._currentState);
},
/**
* @override
* @private
*/
_renderButtons: function() {
this._super.apply(this, arguments);
_.each(["dm_chat", "multi_user_channel"], type => {
const selector = ".o_mail_discuss_button_" + type;
this.$buttons.on("click", selector, this._onAddThread.bind(this));
});
},
/**
* Overrides to only store the thread state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed thread
*
* @override
* @private
*/
_restoreThreadState: function() {
if (this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* Overrides to toggle the visibility of the tabs when a message is selected
*
* @override
* @private
*/
_selectMessage: function() {
this._super.apply(this, arguments);
this.$(".o_mail_mobile_tabs").addClass("o_hidden");
},
/**
* @override
* @private
*/
_setThread: function(threadID) {
const thread = this.call("mail_service", "getThread", threadID);
this._thread = thread;
if (thread.getType() !== "mailbox") {
this.call("mail_service", "openThreadWindow", threadID);
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
/**
* Overrides to only store the thread state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed thread
*
* @override
* @private
*/
_storeThreadState: function() {
if (this._thread && this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* Overrides to toggle the visibility of the tabs when a message is
* unselected
*
* @override
* @private
*/
_unselectMessage: function() {
this._super.apply(this, arguments);
this.$(".o_mail_mobile_tabs").removeClass("o_hidden");
},
/**
* @override
* @private
*/
_updateThreads: function() {
return this._updateContent(this._currentState);
},
/**
* Redraws the content of the client action according to its current state.
*
* @private
* @param {String} type the thread's type to display (e.g. 'mailbox_inbox',
* 'mailbox_starred', 'dm_chat'...).
* @returns {Promise}
*/
_updateContent: function(type) {
const inMailbox = type === "mailbox_inbox" || type === "mailbox_starred";
if (!inMailbox && this._isInInboxTab()) {
// We're leaving the inbox, so store the thread scrolltop
this._storeThreadState();
}
const previouslyInInbox = this._isInInboxTab();
this._currentState = type;
// Fetch content to display
let def = false;
if (inMailbox) {
def = this._fetchAndRenderThread();
} else {
const allChannels = this.call("mail_service", "getChannels");
const channels = _.filter(allChannels, function(channel) {
return channel.getType() === type;
});
def = this.call("mail_service", "getChannelPreviews", channels);
}
return def.then(previews => {
// Update content
if (inMailbox) {
if (!previouslyInInbox) {
this.$(".o_mail_discuss_tab_pane").remove();
this._$mainContent.append(this._threadWidget.$el);
this._$mainContent.append(this._extendedComposer.$el);
}
this._restoreThreadState();
} else {
this._threadWidget.$el.detach();
this._extendedComposer.$el.detach();
const $content = $(
QWeb.render("mail.discuss.MobileTabPane", {
previews: previews,
type: type,
})
);
this._prepareAddThreadInput(
$content.find(".o_mail_add_thread input"),
type
);
this._$mainContent.html($content);
}
// Update control panel
this.$buttons
.find("button")
.removeClass("d-block")
.addClass("d-none");
this.$buttons
.find(".o_mail_discuss_button_" + type)
.removeClass("d-none")
.addClass("d-block");
this.$buttons
.find(".o_mail_discuss_button_mark_all_read")
.toggleClass("d-none", type !== "mailbox_inbox")
.toggleClass("d-block", type === "mailbox_inbox");
this.$buttons
.find(".o_mail_discuss_button_unstar_all")
.toggleClass("d-none", type !== "mailbox_starred")
.toggleClass("d-block", type === "mailbox_starred");
// Update Mailbox page buttons
if (inMailbox) {
this.$(".o_mail_discuss_mobile_mailboxes_buttons").removeClass(
"o_hidden"
);
this.$(".o_mailbox_inbox_item")
.removeClass("btn-primary")
.addClass("btn-secondary");
this.$(".o_mailbox_inbox_item[data-type=" + type + "]")
.removeClass("btn-secondary")
.addClass("btn-primary");
} else {
this.$(".o_mail_discuss_mobile_mailboxes_buttons").addClass(
"o_hidden"
);
}
// Update bottom buttons
this.$(".o_mail_mobile_tab").removeClass("active");
// Mailbox_inbox and mailbox_starred share the same tab
const type_n = type === "mailbox_starred" ? "mailbox_inbox" : type;
this.$(".o_mail_mobile_tab[data-type=" + type_n + "]").addClass(
"active"
);
});
},
// --------------------------------------------------------------------------
// Handlers
// --------------------------------------------------------------------------
/**
* @override
* @private
*/
_onAddThread: function() {
this.$(".o_mail_add_thread")
.show()
.find("input")
.focus();
},
/**
* Switches to the clicked thread in the Inbox page (Inbox or Starred).
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileMailboxItem: function(ev) {
const mailboxID = $(ev.currentTarget).data("type");
this._setThread(mailboxID);
this._updateContent(this._thread.getID());
},
/**
* Switches to another tab.
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileTab: function(ev) {
const type = $(ev.currentTarget).data("type");
if (type === "mailbox") {
const inbox = this.call("mail_service", "getMailbox", "inbox");
this._setThread(inbox);
}
this._updateContent(type);
},
/**
* Opens a thread in a chat window (full screen in mobile).
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileMailPreview: function(ev) {
const threadID = $(ev.currentTarget).data("preview-id");
this.call("mail_service", "openThreadWindow", threadID);
},
});
});

View File

@ -0,0 +1,494 @@
odoo.define("web_responsive.KanbanRendererMobile", function (require) {
"use strict";
/**
* The purpose of this file is to improve the UX of grouped kanban views in
* mobile. It includes the KanbanRenderer (in mobile only) to only display one
* column full width, and enables the swipe to browse to the other columns.
* Moreover, records in columns are lazy-loaded.
*/
var config = require("web.config");
var core = require("web.core");
var KanbanRenderer = require("web.KanbanRenderer");
var KanbanView = require("web.KanbanView");
var KanbanQuickCreate = require("web.kanban_column_quick_create");
var _t = core._t;
var qweb = core.qweb;
if (!config.device.isMobile) {
return;
}
KanbanQuickCreate.include({
init() {
this._super.apply(this, arguments);
this.isMobile = true;
},
});
KanbanView.include({
init() {
this._super.apply(this, arguments);
this.jsLibs.push("/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js");
},
});
KanbanRenderer.include({
custom_events: _.extend({}, KanbanRenderer.prototype.custom_events || {}, {
quick_create_column_created: "_onColumnAdded",
}),
events: _.extend({}, KanbanRenderer.prototype.events, {
"click .o_kanban_mobile_tab": "_onMobileTabClicked",
"click .o_kanban_mobile_add_column": "_onMobileQuickCreateClicked",
}),
ANIMATE: true, // Allows to disable animations for the tests
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.activeColumnIndex = 0; // Index of the currently displayed column
this._scrollPosition = null;
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to restore the scroll position like it was
* when the renderer has been last detached.
*
* @override
*/
on_attach_callback: function () {
if (
this._scrollPosition &&
this.state.groupedBy.length &&
this.widgets.length
) {
var $column = this.widgets[this.activeColumnIndex].$el;
$column.scrollLeft(this._scrollPosition.left);
$column.scrollTop(this._scrollPosition.top);
}
this._computeTabPosition();
this._super.apply(this, arguments);
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to store the scroll position, so that we can
* restore it if the renderer is re-attached to the DOM later.
*
* @override
*/
on_detach_callback: function () {
if (this.state.groupedBy.length && this.widgets.length) {
var $column = this.widgets[this.activeColumnIndex].$el;
this._scrollPosition = {
left: $column.scrollLeft(),
top: $column.scrollTop(),
};
} else {
this._scrollPosition = null;
}
this._super.apply(this, arguments);
},
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
/**
* Displays the quick create record in the active column
* override to open quick create record in current active column
*
* @override
* @returns {Promise}
*/
addQuickCreate: function () {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this._onMobileQuickCreateClicked();
}
return this.widgets[this.activeColumnIndex].addQuickCreate();
},
/**
* Overrides to restore the left property and the scrollTop on the updated
* column, and to enable the swipe handlers
*
* @override
*/
updateColumn: function (localID) {
var index = _.findIndex(this.widgets, {db_id: localID});
var $column = this.widgets[index].$el;
var scrollTop = $column.scrollTop();
return (
this._super
.apply(this, arguments)
.then(() => this._layoutUpdate(false))
// Required when clicking on 'Load More'
.then(() => $column.scrollTop(scrollTop))
.then(() => this._enableSwipe())
);
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* Check if we use the quick create on mobile
* @returns {Boolean}
* @private
*/
_canCreateColumn: function () {
return this.quickCreateEnabled && this.quickCreate && this.widgets.length;
},
/**
* Update the columns positions
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_computeColumnPosition: function (animate) {
if (this.widgets.length) {
// Check rtl to compute correct css value
const rtl = _t.database.parameters.direction === "rtl";
// Display all o_kanban_group
this.$(".o_kanban_group").show();
const $columnAfter = this._toNode(
this.widgets.filter(
(widget, index) => index > this.activeColumnIndex
)
);
const promiseAfter = this._updateColumnCss(
$columnAfter,
rtl ? {right: "100%"} : {left: "100%"},
animate
);
const $columnBefore = this._toNode(
this.widgets.filter(
(widget, index) => index < this.activeColumnIndex
)
);
const promiseBefore = this._updateColumnCss(
$columnBefore,
rtl ? {right: "-100%"} : {left: "-100%"},
animate
);
const $columnCurrent = this._toNode(
this.widgets.filter(
(widget, index) => index === this.activeColumnIndex
)
);
const promiseCurrent = this._updateColumnCss(
$columnCurrent,
rtl ? {right: "0%"} : {left: "0%"},
animate
);
promiseAfter
.then(promiseBefore)
.then(promiseCurrent)
.then(() => {
$columnAfter.hide();
$columnBefore.hide();
});
}
},
/**
* Define the o_current class to the current selected kanban (column & tab)
*
* @private
*/
_computeCurrentColumn: function () {
if (this.widgets.length) {
var column = this.widgets[this.activeColumnIndex];
if (!column) {
return;
}
var columnID = column.id || column.db_id;
this.$(
".o_kanban_mobile_tab.o_current, .o_kanban_group.o_current"
).removeClass("o_current");
this.$(
'.o_kanban_group[data-id="' +
columnID +
'"], ' +
'.o_kanban_mobile_tab[data-id="' +
columnID +
'"]'
).addClass("o_current");
}
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabPosition: function () {
this._computeTabJustification();
this._computeTabScrollPosition();
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabScrollPosition: function () {
if (this.widgets.length) {
var lastItemIndex = this.widgets.length - 1;
var moveToIndex = this.activeColumnIndex;
var scrollToLeft = 0;
for (var i = 0; i < moveToIndex; i++) {
var columnWidth = this._getTabWidth(this.widgets[i]);
// Apply
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
var partialWidth = 0.75;
scrollToLeft += columnWidth * partialWidth;
} else {
scrollToLeft += columnWidth;
}
}
// Apply the scroll x on the tabs
// XXX in case of RTL, should we use scrollRight?
this.$(".o_kanban_mobile_tabs").scrollLeft(scrollToLeft);
}
},
/**
* Compute the justify content of the kanban tab headers
*
* @private
*/
_computeTabJustification: function () {
if (this.widgets.length) {
var self = this;
// Use to compute the sum of the width of all tab
var widthChilds = this.widgets.reduce(function (total, column) {
return total + self._getTabWidth(column);
}, 0);
// Apply a space around between child if the parent length is higher then the sum of the child width
var $tabs = this.$(".o_kanban_mobile_tabs");
$tabs.toggleClass(
"justify-content-between",
$tabs.outerWidth() >= widthChilds
);
}
},
/**
* Enables swipe event on the current column
*
* @private
*/
_enableSwipe: function () {
var self = this;
var step = _t.database.parameters.direction === "rtl" ? -1 : 1;
this.$el.swipe({
excludedElements: ".o_kanban_mobile_tabs",
swipeLeft: function () {
var moveToIndex = self.activeColumnIndex + step;
if (moveToIndex < self.widgets.length) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
swipeRight: function () {
var moveToIndex = self.activeColumnIndex - step;
if (moveToIndex > -1) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
});
},
/**
* Retrieve the outerWidth of a given widget column
*
* @param {KanbanColumn} column
* @returns {integer} outerWidth of the found column
* @private
*/
_getTabWidth: function (column) {
var columnID = column.id || column.db_id;
return this.$(
'.o_kanban_mobile_tab[data-id="' + columnID + '"]'
).outerWidth();
},
/**
* Update the kanban layout
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_layoutUpdate: function (animate) {
this._computeCurrentColumn();
this._computeTabPosition();
this._computeColumnPosition(animate);
this._enableSwipe();
},
/**
* Moves to the given kanban column
*
* @private
* @param {integer} moveToIndex index of the column to move to
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise} resolved when the new current group has been loaded
* and displayed
*/
_moveToGroup: function (moveToIndex, animate) {
var self = this;
if (moveToIndex >= 0 && moveToIndex < this.widgets.length) {
this.activeColumnIndex = moveToIndex;
}
var column = this.widgets[this.activeColumnIndex];
this._enableSwipe();
if (!column.data.isOpen) {
this.trigger_up("column_toggle_fold", {
db_id: column.db_id,
onSuccess: () => self._layoutUpdate(animate),
});
} else {
this._layoutUpdate(animate);
}
return Promise.resolve();
},
/**
* @override
* @private
*/
_renderExampleBackground: function () {
// Override to avoid display of example background
},
/**
* @override
* @private
*/
_renderGrouped: function (fragment) {
var self = this;
var newFragment = document.createDocumentFragment();
this._super.apply(this, [newFragment]);
this.defs.push(
Promise.all(this.defs).then(function () {
var data = [];
_.each(self.state.data, function (group) {
if (!group.value) {
group = _.extend({}, group, {value: _t("Undefined")});
data.unshift(group);
} else {
data.push(group);
}
});
var kanbanColumnContainer = document.createElement("div");
kanbanColumnContainer.classList.add("o_kanban_columns_content");
kanbanColumnContainer.appendChild(newFragment);
fragment.appendChild(kanbanColumnContainer);
$(
qweb.render("KanbanView.MobileTabs", {
data: data,
quickCreateEnabled: self._canCreateColumn(),
})
).prependTo(fragment);
})
);
},
/**
* @override
* @private
*/
_renderView: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
if (self.state.groupedBy.length) {
// Force first column for kanban view, because the groupedBy can be changed
return self._moveToGroup(0);
}
if (self._canCreateColumn()) {
self._onMobileQuickCreateClicked();
}
return Promise.resolve();
});
},
/**
* Retrieve the Jquery node (.o_kanban_group) for a list of a given widgets
*
* @private
* @param widgets
* @returns {jQuery} the matching .o_kanban_group widgets
*/
_toNode: function (widgets) {
const selectorCss = widgets
.map(
(widget) =>
'.o_kanban_group[data-id="' + (widget.id || widget.db_id) + '"]'
)
.join(", ");
return this.$(selectorCss);
},
/**
* Update the given column to the updated positions
*
* @private
* @param $column The jquery column
* @param cssProperties Use to update column
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise}
*/
_updateColumnCss: function ($column, cssProperties, animate) {
if (animate) {
return new Promise((resolve) =>
$column.animate(cssProperties, "fast", resolve)
);
}
$column.css(cssProperties);
return Promise.resolve();
},
// --------------------------------------------------------------------------
// Handlers
// --------------------------------------------------------------------------
/**
* @private
*/
_onColumnAdded: function () {
this._computeTabPosition();
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
},
/**
* @private
*/
_onMobileQuickCreateClicked: function (event) {
if (event) {
event.stopPropagation();
}
this.$(".o_kanban_group").toggle();
this.quickCreate.toggleFold();
},
/**
* @private
* @param {MouseEvent} event
*/
_onMobileTabClicked: function (event) {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
this._moveToGroup($(event.currentTarget).index(), true);
},
});
});

View File

@ -1,7 +1,8 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2018 Tecnativa - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive", function(require) {
odoo.define("web_responsive", function (require) {
"use strict";
const ActionManager = require("web.ActionManager");
@ -13,16 +14,23 @@ odoo.define("web_responsive", function(require) {
const FormRenderer = require("web.FormRenderer");
const Menu = require("web.Menu");
const RelationalFields = require("web.relational_fields");
const Chatter = require("mail.Chatter");
const ListRenderer = require("web.ListRenderer");
const DocumentViewer = require("mail.DocumentViewer");
const CalendarRenderer = require("web.CalendarRenderer");
const patchMixin = require("web.patchMixin");
const AttachmentViewer = require("mail/static/src/components/attachment_viewer/attachment_viewer.js");
const PatchableAttachmentViewer = patchMixin(AttachmentViewer);
const ControlPanel = require("web.ControlPanel");
const SearchPanel = require("web/static/src/js/views/search_panel.js");
/* global owl */
const {QWeb, Context} = owl;
const {useState, useContext} = owl.hooks;
/* Hide AppDrawer in desktop and mobile modes.
* To avoid delays in pages with a lot of DOM nodes we make
* sub-groups' with 'querySelector' to improve the performance.
*/
function closeAppDrawer() {
_.defer(function() {
_.defer(function () {
// Need close AppDrawer?
var menu_apps_dropdown = document.querySelector(".o_menu_apps .dropdown");
$(menu_apps_dropdown)
@ -115,7 +123,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
init: function(parent, menuData) {
init: function (parent, menuData) {
this._super.apply(this, arguments);
// Keep base64 icon for main menus
for (const n in this._apps) {
@ -130,7 +138,7 @@ odoo.define("web_responsive", function(require) {
/**
* @override
*/
start: function() {
start: function () {
this.$search_container = this.$(".search-container");
this.$search_input = this.$(".search-input input");
this.$search_results = this.$(".search-results");
@ -142,7 +150,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_onAppsMenuItemClicked: function(ev) {
_onAppsMenuItemClicked: function (ev) {
this._super.apply(this, arguments);
ev.preventDefault();
ev.stopPropagation();
@ -157,7 +165,7 @@ odoo.define("web_responsive", function(require) {
* @returns {Object}
* Menu definition, plus extra needed keys.
*/
_menuInfo: function(key) {
_menuInfo: function (key) {
const original = this._searchableMenus[key];
return _.extend(
{
@ -170,7 +178,7 @@ odoo.define("web_responsive", function(require) {
/**
* Autofocus on search field on big screens.
*/
_searchFocus: function() {
_searchFocus: function () {
if (!config.device.isMobile) {
// This timeout is necessary since the menu has a 100ms fading animation
setTimeout(() => this.$search_input.focus(), 100);
@ -180,7 +188,7 @@ odoo.define("web_responsive", function(require) {
/**
* Reset search input and results
*/
_searchReset: function() {
_searchReset: function () {
this.$search_container.removeClass("has-results");
this.$search_results.empty();
this.$search_input.val("");
@ -189,8 +197,8 @@ odoo.define("web_responsive", function(require) {
/**
* Schedule a search on current menu items.
*/
_searchMenusSchedule: function() {
this._search_def = new Promise(resolve => {
_searchMenusSchedule: function () {
this._search_def = new Promise((resolve) => {
setTimeout(resolve, 50);
});
this._search_def.then(this._searchMenus.bind(this));
@ -199,7 +207,7 @@ odoo.define("web_responsive", function(require) {
/**
* Search among available menu items, and render that search.
*/
_searchMenus: function() {
_searchMenus: function () {
const query = this.$search_input.val();
if (query === "") {
this.$search_container.removeClass("has-results");
@ -224,7 +232,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {jQuery.Event} event
*/
_searchResultChosen: function(event) {
_searchResultChosen: function (event) {
event.preventDefault();
event.stopPropagation();
const $result = $(event.currentTarget),
@ -238,7 +246,7 @@ odoo.define("web_responsive", function(require) {
previous_menu_id: data.parentId,
});
// Find app that owns the chosen menu
const app = _.find(this._apps, function(_app) {
const app = _.find(this._apps, function (_app) {
return text.indexOf(_app.name + suffix) === 0;
});
// Update navbar menus
@ -250,7 +258,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {jQuery.Event} event
*/
_searchResultsNavigate: function(event) {
_searchResultsNavigate: function (event) {
// Find current results and active element (1st by default)
const all = this.$search_results.find(".o-menu-search-result"),
pre_focused = all.filter(".active") || $(all[0]);
@ -301,7 +309,7 @@ odoo.define("web_responsive", function(require) {
/*
* Control if AppDrawer can be closed
*/
_hideAppsMenu: function() {
_hideAppsMenu: function () {
return !this.$("input").is(":focus");
},
});
@ -313,7 +321,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
canBeDiscarded: function(recordID) {
canBeDiscarded: function (recordID) {
if (this.model.isDirty(recordID || this.handle)) {
closeAppDrawer();
}
@ -332,7 +340,7 @@ odoo.define("web_responsive", function(require) {
Menu.prototype.events
),
start: function() {
start: function () {
this.$menu_toggle = this.$(".o-menu-toggle");
return this._super.apply(this, arguments);
},
@ -340,7 +348,7 @@ odoo.define("web_responsive", function(require) {
/**
* Hide menus for current app if you're in mobile
*/
_hideMobileSubmenus: function() {
_hideMobileSubmenus: function () {
if (
config.device.isMobile &&
this.$menu_toggle.is(":visible") &&
@ -355,7 +363,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {ClickEvent} ev
*/
_onClickMenuItem: function(ev) {
_onClickMenuItem: function (ev) {
ev.stopPropagation();
},
@ -364,7 +372,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_updateMenuBrand: function() {
_updateMenuBrand: function () {
if (!config.device.isMobile) {
return this._super.apply(this, arguments);
}
@ -377,10 +385,10 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_setState: function() {
_setState: function () {
this._super.apply(this, arguments);
if (config.device.isMobile) {
_.map(this.status_information, value => {
_.map(this.status_information, (value) => {
value.fold = true;
});
}
@ -389,7 +397,7 @@ odoo.define("web_responsive", function(require) {
// Sticky Column Selector
ListRenderer.include({
_renderView: function() {
_renderView: function () {
const self = this;
return this._super.apply(this, arguments).then(() => {
const $col_selector = self.$el.find(
@ -402,7 +410,7 @@ odoo.define("web_responsive", function(require) {
});
},
_onToggleOptionalColumnDropdown: function(ev) {
_onToggleOptionalColumnDropdown: function (ev) {
// FIXME: For some strange reason the 'stopPropagation' call
// in the main method don't work. Invoking here the same method
// does the expected behavior... O_O!
@ -420,17 +428,16 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_renderHeaderButtons: function() {
_renderHeaderButtons: function () {
const $buttons = this._super.apply(this, arguments);
if (
!config.device.isMobile ||
!$buttons.is(":has(>:not(.o_invisible_modifier))")
$buttons.children("button:not(.o_invisible_modifier)").length <= 2
) {
return $buttons;
}
// $buttons must be appended by JS because all events are bound
$buttons.addClass("dropdown-menu");
const $dropdown = $(
core.qweb.render("web_responsive.MenuStatusbarButtons")
);
@ -439,18 +446,13 @@ odoo.define("web_responsive", function(require) {
},
});
// Chatter Hide Composer
Chatter.include({
_openComposer: function(options) {
if (
this._composer &&
options.isLog === this._composer.options.isLog &&
this._composer.$el.is(":visible")
) {
this._closeComposer(false);
} else {
this._super.apply(this, arguments);
CalendarRenderer.include({
_getFullCalendarOptions: function () {
var options = this._super.apply(this, arguments);
if (config.device.isMobile) {
options.views.dayGridMonth.columnHeaderFormat = "ddd";
}
return options;
},
});
@ -459,7 +461,7 @@ odoo.define("web_responsive", function(require) {
/**
* @override
*/
_appendController: function() {
_appendController: function () {
this._super.apply(this, arguments);
closeAppDrawer();
},
@ -495,7 +497,7 @@ odoo.define("web_responsive", function(require) {
* @returns {keyEvent}
* Altered event object
*/
_shiftPressed: function(keyEvent) {
_shiftPressed: function (keyEvent) {
const alt = keyEvent.altKey || keyEvent.key === "Alt",
newEvent = _.extend({}, keyEvent),
shift = keyEvent.shiftKey || keyEvent.key === "Shift";
@ -509,11 +511,11 @@ odoo.define("web_responsive", function(require) {
return newEvent;
},
_onKeyDown: function(keyDownEvent) {
_onKeyDown: function (keyDownEvent) {
return this._super(this._shiftPressed(keyDownEvent));
},
_onKeyUp: function(keyUpEvent) {
_onKeyUp: function (keyUpEvent) {
return this._super(this._shiftPressed(keyUpEvent));
},
};
@ -522,44 +524,106 @@ odoo.define("web_responsive", function(require) {
// `KeyboardNavigationMixin` is used upstream
AbstractWebClient.include(KeyboardNavigationShiftAltMixin);
// DocumentViewer: Add support to maximize/minimize
DocumentViewer.include({
// Widget 'keydown' and 'keyup' events are only dispatched when
// this.$el is active, but now the modal have buttons that can obtain
// the focus. For this reason we now listen core events, that are
// dispatched every time.
events: _.extend(
_.omit(DocumentViewer.prototype.events, ["keydown", "keyup"]),
{
"click .o_maximize_btn": "_onClickMaximize",
"click .o_minimize_btn": "_onClickMinimize",
"shown.bs.modal": "_onShownModal",
}
),
start: function() {
core.bus.on("keydown", this, this._onKeydown);
core.bus.on("keyup", this, this._onKeyUp);
return this._super.apply(this, arguments);
},
destroy: function() {
core.bus.off("keydown", this, this._onKeydown);
core.bus.off("keyup", this, this._onKeyUp);
this._super.apply(this, arguments);
},
_onShownModal: function() {
// Disable auto-focus to allow to use controls in edit mode.
// This only affects the active modal.
// More info: https://stackoverflow.com/a/14795256
$(document).off("focusin.modal");
},
_onClickMaximize: function() {
this.$el.removeClass("o_responsive_document_viewer");
},
_onClickMinimize: function() {
this.$el.addClass("o_responsive_document_viewer");
},
// TODO: use default odoo device context when it will be realized
const deviceContext = new Context({
isMobile: config.device.isMobile,
size_class: config.device.size_class,
SIZES: config.device.SIZES,
});
window.addEventListener(
"resize",
owl.utils.debounce(() => {
const state = deviceContext.state;
if (state.isMobile !== config.device.isMobile) {
state.isMobile = !state.isMobile;
}
if (state.size_class !== config.device.size_class) {
state.size_class = config.device.size_class;
}
}, 15)
);
// Patch attachment viewer to add min/max buttons capability
PatchableAttachmentViewer.patch("web_responsive.AttachmentViewer", (T) => {
class AttachmentViewerPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
maximized: false,
});
}
// Disable auto-close to allow to use form in edit mode.
isCloseable() {
return false;
}
}
return AttachmentViewerPatchResponsive;
});
QWeb.components.AttachmentViewer = PatchableAttachmentViewer;
// Patch control panel to add states for mobile quick search
ControlPanel.patch("web_responsive.ControlPanelMobile", (T) => {
class ControlPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
mobileSearchMode: "",
});
this.device = useContext(deviceContext);
}
}
return ControlPanelPatchResponsive;
});
// Patch search panel to add functionality for mobile view
SearchPanel.patch("web_responsive.SearchPanelMobile", (T) => {
class SearchPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state.mobileSearch = false;
this.device = useContext(deviceContext);
}
getActiveSummary() {
const selection = [];
for (const filter of this.model.get("sections")) {
let filterValues = [];
if (filter.type === "category") {
if (filter.activeValueId) {
const parentIds = this._getAncestorValueIds(
filter,
filter.activeValueId
);
filterValues = [...parentIds, filter.activeValueId].map(
(valueId) => filter.values.get(valueId).display_name
);
}
} else {
let values = [];
if (filter.groups) {
values = [
...filter.groups.values().map((g) => g.values),
].flat();
}
if (filter.values) {
values = [...filter.values.values()];
}
filterValues = values
.filter((v) => v.checked)
.map((v) => v.display_name);
}
if (filterValues.length) {
selection.push({
values: filterValues,
icon: filter.icon,
color: filter.color,
type: filter.type,
});
}
}
return selection;
}
}
return SearchPanelPatchResponsive;
});
return {
deviceContext: deviceContext,
};
});

View File

@ -7,8 +7,9 @@
<t t-jquery=".o_app" t-operation="attributes">
<attribute
name="t-attf-href"
t-translation="off"
>#menu_id=#{app.menuID}&amp;action_id=#{app.actionID}</attribute>
<attribute name="draggable">false</attribute>
<attribute name="draggable" t-translation="off">false</attribute>
</t>
<!-- App icons should be more than a text -->
<t t-jquery=".o_app &gt; t" t-operation="replace">

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<template>
<t t-inherit="mail.Dialog" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_Dialog')]" position="attributes">
<attribute
name="t-attf-class"
t-translation="off"
>o_Dialog_{{dialog.record['constructor'].name}}</attribute>
</xpath>
</t>
<t t-inherit="mail.AttachmentViewer" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_AttachmentViewer')]" position="attributes">
<attribute
name="t-att-class"
t-translation="off"
>state.maximized ? 'o_AttachmentViewer_maximized' : ''</attribute>
</xpath>
<xpath
expr="//div[hasclass('o_AttachmentViewer_header')]/div[hasclass('o-autogrow')]"
position="after"
>
<div
t-if="!state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMaximize"
t-on-click="state.maximized=true"
role="button"
title="Maximize"
aria-label="Maximize"
>
<i class="fa fa-fw fa-window-maximize" role="img" />
</div>
<div
t-if="state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMinimize"
t-on-click="state.maximized=false"
role="button"
title="Minimize"
aria-label="Minimize"
>
<i class="fa fa-fw fa-window-minimize" role="img" />
</div>
</xpath>
</t>
</template>

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.ControlPanel" t-inherit-mode="extension" owl="1">
<xpath expr="//nav[hasclass('o_cp_switch_buttons')]" position="replace">
<nav
t-if="props.views.length gt 1"
class="btn-group o_cp_switch_buttons"
role="toolbar"
aria-label="View switcher"
>
<t
t-set="collapse_switchview"
t-value="device.size_class &lt;= device.SIZES.LG"
/>
<button
t-if="collapse_switchview"
class="btn btn-link btn-sm"
data-toggle="dropdown"
aria-expanded="false"
>
<span
t-attf-class="fa fa-lg o_switch_view o_{{ env.view.type }} {{ props.views.filter(view => view.type === env.view.type)[0].icon }}"
/>
</button>
<ul
t-if="collapse_switchview"
class="dropdown-menu dropdown-menu-right list-inline"
>
<li t-foreach="props.views" t-as="view" t-key="view.type">
<t t-call="web.ViewSwitcherButton" />
</li>
</ul>
<t
t-if="!collapse_switchview"
t-foreach="props.views"
t-as="view"
t-key="view.type"
>
<t t-call="web.ViewSwitcherButton" />
</t>
</nav>
</xpath>
<xpath expr="//div[hasclass('o_searchview')]" position="replace">
<div
t-if="props.withSearchBar"
class="o_searchview"
t-att-class="state.mobileSearchMode == 'quick' ? 'o_searchview_quick' : 'o_searchview_mobile'"
role="search"
aria-autocomplete="list"
t-on-click.self="state.mobileSearchMode = device.isMobile ? 'quick' : ''"
>
<t t-if="!device.isMobile">
<i
class="o_searchview_icon fa fa-search"
title="Search..."
role="img"
aria-label="Search..."
/>
<SearchBar fields="fields" />
</t>
<t t-if="device.isMobile and state.mobileSearchMode == 'quick'">
<button
class="btn btn-link fa fa-arrow-left"
t-on-click.stop="state.mobileSearchMode = ''"
/>
<SearchBar fields="fields" />
<button
class="btn fa fa-filter"
t-on-click.stop="state.mobileSearchMode = 'full'"
/>
</t>
<t
t-if="device.isMobile and state.mobileSearchMode == 'full'"
t-call="web_responsive.MobileSearchView"
/>
<t t-if="device.isMobile and state.mobileSearchMode == ''">
<button
class="btn btn-link fa fa-search"
t-on-click.stop="state.mobileSearchMode = 'quick'"
/>
</t>
</div>
</xpath>
<xpath expr="//div[hasclass('o_cp_top_left')]" position="attributes">
<attribute
name="t-att-class"
t-translation="off"
>device.isMobile and state.mobileSearchMode == 'quick' ? 'o_hidden' : ''</attribute>
</xpath>
<xpath expr="//div[hasclass('o_search_options')]" position="attributes">
<attribute name="t-if" t-translation="off">!device.isMobile</attribute>
<attribute
name="t-att-class"
t-translation="off"
>device.size_class == device.SIZES.MD ? 'o_search_options_hide_labels' : ''</attribute>
</xpath>
</t>
<t t-name="web_responsive.MobileSearchView" owl="1">
<div class="o_mobile_search">
<div class="o_mobile_search_header">
<span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="state.mobileSearchMode = 'quick'"
>
<i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong>
</span>
<span
class="float-right o_mobile_search_clear_facets mt16 mr16"
t-on-click.stop="model.dispatch('clearQuery')"
>
<t>CLEAR</t>
</span>
</div>
<SearchBar fields="fields" />
<div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16">
<FilterMenu
t-if="props.searchMenuTypes.includes('filter')"
class="o_filter_menu"
fields="fields"
/>
<GroupByMenu
t-if="props.searchMenuTypes.includes('groupBy')"
class="o_group_by_menu"
fields="fields"
/>
<ComparisonMenu
t-if="props.searchMenuTypes.includes('comparison') and model.get('filters', f => f.type === 'comparison').length"
class="o_comparison_menu"
/>
<FavoriteMenu
t-if="props.searchMenuTypes.includes('favorite')"
class="o_favorite_menu"
/>
</div>
<div
class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="state.mobileSearchMode = ''"
>
<t>SEE RESULT</t>
</div>
</div>
</t>
</templates>

View File

@ -7,9 +7,10 @@
t-jquery=".o_mail_discuss_button_multi_user_channel"
t-operation="attributes"
>
<attribute
name="class"
>btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block d-none</attribute>
<attribute name="class" t-translation="off">
btn btn-secondary o_mail_discuss_button_multi_user_channel d-md-block
d-none
</attribute>
</t>
</t>
</template>

View File

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<template>
<t t-extend="DocumentViewer">
<t t-jquery=".o_modal_fullscreen" t-operation="attributes">
<attribute
name="class"
>modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer</attribute>
<attribute name="data-backdrop">false</attribute>
</t>
</t>
<t t-extend="DocumentViewer.Content">
<t t-jquery=".o_close_btn" t-operation="replace">
<div class="o_buttons float-right mr8">
<a
role="button"
class="mr8 o_maximize_btn"
tabindex="0"
aria-label="Maximize"
title="Maximize"
>
<i class="fa fa-window-maximize" />
</a>
<a
role="button"
class="mr8 o_minimize_btn"
tabindex="0"
aria-label="Minimize"
title="Minimize"
>
<i class="fa fa-window-minimize" />
</a>
<a
role="button"
class="o_close_btn"
tabindex="0"
aria-label="Close"
title="Close"
>
<i class="fa fa-close" />
</a>
</div>
</t>
</t>
</template>

View File

@ -98,40 +98,13 @@
</t>
</t>
</t>
<t t-extend="Sidebar">
<!-- Replace some common sections by icons in mobile -->
<t
t-jquery=".o_dropdown_toggler_btn t[t-esc='section.label']"
t-operation="replace"
>
<t t-set="label" t-value="section.label" />
<t t-if="section.name == 'files'">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'paperclip'" />
</t>
<t t-extend="CalendarView.navigation_buttons">
<!-- Add responsive icons to buttons -->
<t t-jquery=".o_calendar_button_today" t-operation="inner">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'calendar-check-o'" />
<t t-set="label">Today</t>
</t>
<t t-elif="section.name == 'print'">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'print'" />
</t>
</t>
<t t-elif="section.name == 'other'">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'wrench'" />
</t>
</t>
<t t-else="">
<span t-esc="label" />
</t>
</t>
</t>
<t t-extend="mail.Chatter">
<t t-jquery=".o_chatter_topbar" t-operation="replace">
<div class="o_chatter_header_container">
<div class="o_chatter_topbar">
<div class="o_topbar_right_area" />
</div>
</div>
</t>
</t>
</templates>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2017-2018 Tecnativa - Jairo Llopis
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<template>
<templates>
<t t-extend="Menu">
<t t-jquery=".o_menu_apps" t-operation="after">
<!-- Hamburger button to show submenus in sm screens -->
@ -14,4 +14,4 @@
</button>
</t>
</t>
</template>
</templates>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.SearchPanel" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('o_search_panel')]" position="inside">
<div
t-if="device.isMobile"
class="o_search_panel_summary"
t-on-click.stop="state.mobileSearch = true"
>
<div class="d-flex flex-wrap align-items-center">
<i class="fa fa-fw fa-filter mr-1" />
<t t-set="filters" t-value="getActiveSummary()" />
<span t-foreach="filters" t-as="filter" class="mx-1">
<i
t-if="filter.icon"
t-attf-class="fa {{ filter.icon }} mr-2"
t-att-style="filter.color and ('color: ' + filter.color)"
/>
<t
t-esc="filter.values.join(filter.type == 'category' ? ' / ' : ', ')"
/>
</span>
<t t-if="!filters.length">All</t>
</div>
</div>
<div
class="o_search_panel_content"
t-att-class="device.isMobile ? (state.mobileSearch ? 'o_mobile_search' : 'd-none'): ''"
/>
</xpath>
<xpath expr="//div[hasclass('o_search_panel_content')]" position="inside">
<div t-if="device.isMobile" class="o_mobile_search_header">
<span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="state.mobileSearch = false"
>
<i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong>
</span>
</div>
<xpath expr="//section" position="move" />
<div
t-if="device.isMobile"
class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="state.mobileSearch = false"
>
<t>SEE RESULT</t>
</div>
</xpath>
</t>
</templates>

View File

@ -10,18 +10,28 @@
name="Open Mobile Assets"
inherit_id="web.assets_backend"
>
<xpath expr=".">
<xpath expr="//link[last()]" position="after">
<link
rel="stylesheet"
href="/web_responsive/static/src/css/web_responsive.scss"
/>
<link
rel="stylesheet"
href="/web_responsive/static/src/css/search_view_mobile.scss"
/>
<link
rel="stylesheet"
href="/web_responsive/static/src/css/kanban_view_mobile.scss"
/>
</xpath>
<xpath expr="//script[last()]" position="after">
<script
type="application/javascript"
src="/web_responsive/static/src/js/web_responsive.js"
/>
<script
type="application/javascript"
src="/web_responsive/static/src/js/discuss.js"
src="/web_responsive/static/src/js/kanban_renderer_mobile.js"
/>
</xpath>
</template>

View File

@ -17,4 +17,16 @@
/>
</xpath>
</template>
<template
id="responsive_web_layout"
inherit_id="web.layout"
name="Responsive Layout"
>
<xpath expr="//meta[last()]" position="after">
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
</xpath>
</template>
</odoo>