3
0
Fork 0

[MIG] web_responsive: Migration to 16.0

Fix for optional dropdown checkbox in list view

Made Changes for compatibility with web_chatter_position module

Fix for attachment delete dialog
16.0
anjeel.haria 2023-03-01 15:35:45 +01:00
parent da62162a0c
commit beda62914b
42 changed files with 930 additions and 1868 deletions

View File

@ -14,13 +14,13 @@ Web Responsive
:target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html :target: http://www.gnu.org/licenses/lgpl-3.0-standalone.html
:alt: License: LGPL-3 :alt: License: LGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fweb-lightgray.png?logo=github
:target: https://github.com/OCA/web/tree/15.0/web_responsive :target: https://github.com/OCA/web/tree/16.0/web_responsive
:alt: OCA/web :alt: OCA/web
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png .. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/web-15-0/web-15-0-web_responsive :target: https://translation.odoo-community.org/projects/web-16-0/web-16-0-web_responsive
:alt: Translate me on Weblate :alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png .. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/162/15.0 :target: https://runbot.odoo-community.org/runbot/162/16.0
:alt: Try me on Runbot :alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5| |badge1| |badge2| |badge3| |badge4| |badge5|
@ -31,83 +31,83 @@ This module adds responsiveness to web backend.
* New navigation with the fullscreen app menu * New navigation with the fullscreen app menu
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appmenu.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appmenu.gif
* Quick menu search inside the app menu * Quick menu search inside the app menu
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif
* Sticky header & footer in list view * Sticky header & footer in list view
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif
* Sticky statusbar in form view * Sticky statusbar in form view
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif
* Bigger checkboxes in list view * Bigger checkboxes in list view
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif
* Increase the size of the labels in extra large screens * Increase the size of the labels in extra large screens
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_small.png .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/label_size_small.png
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_large.png .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/label_size_large.png
**Features for mobile**: **Features for mobile**:
* App-specific submenus are shown on full screen when toggling them from the * App-specific submenus are shown on full screen when toggling them from the
"hamburger" menu "hamburger" menu
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/hamburger.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/hamburger.gif
* User-specific submenus are shown on full screen when toggling them from the * User-specific submenus are shown on full screen when toggling them from the
"avatar" menu "avatar" menu
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/usermenu.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/usermenu.gif
* View type picker dropdown displays comfortably * View type picker dropdown displays comfortably
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/viewtype.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/viewtype.gif
* Top app bar is always visible, but the control panel is hidden when * Top app bar is always visible, but the control panel is hidden when
scrolling down, to save some valuable vertical space scrolling down, to save some valuable vertical space
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/navbar.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/navbar.gif
* Form status bar action and status buttons are collapsed in dropdowns. * Form status bar action and status buttons are collapsed in dropdowns.
Other control panel buttons use icons to save space. Other control panel buttons use icons to save space.
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/form_buttons.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/form_buttons.gif
* Breadcrumbs navigation is collapsed with a "back arrow" button. * Breadcrumbs navigation is collapsed with a "back arrow" button.
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/breadcrumbs.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/breadcrumbs.gif
* Search panel is collapsed to mobile version on small screens. * Search panel is collapsed to mobile version on small screens.
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/search_panel.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/search_panel.gif
* Followers and send button is displayed on mobile. Avatar is hidden. * Followers and send button is displayed on mobile. Avatar is hidden.
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter.gif
* Scrollable dropdowns * Scrollable dropdowns
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/dropdown_scroll.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/dropdown_scroll.gif
* Kanban interface adopted to mobile * Kanban interface adopted to mobile
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/kanban.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/kanban.gif
* Calendar interface adopted to mobile * Calendar interface adopted to mobile
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/calendar.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/calendar.gif
* Interface is adapted dynamically on device rotation * Interface is adapted dynamically on device rotation
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/device_rotation.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/device_rotation.gif
* Big inputs on form in edit mode * Big inputs on form in edit mode
@ -120,29 +120,29 @@ This module adds responsiveness to web backend.
accessible by fingers of one hand. accessible by fingers of one hand.
F.x. `Alt + S` for `Save` F.x. `Alt + S` for `Save`
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/shortcuts.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/shortcuts.gif
* Autofocus on search menu box when opening the app menu * Autofocus on search menu box when opening the app menu
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif
* Full width form sheets * Full width form sheets
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif
* Set chatter on the side of the screen, optional per user * Set chatter on the side of the screen, optional per user
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_sided.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter_sided.gif
* Sticky chatter topbar * Sticky chatter topbar
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_topbar.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter_topbar.gif
* When the chatter is configured on the side part, the document viewer fills that * When the chatter is configured on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button. width preview clicking on the new maximize button.
.. image:: https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/document_viewer.gif .. image:: https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/document_viewer.gif
**Table of contents** **Table of contents**
@ -170,7 +170,7 @@ Bug Tracker
Bugs are tracked on `GitHub Issues <https://github.com/OCA/web/issues>`_. 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. 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 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:%2015.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:%2016.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. Do not contact contributors directly about support or help with technical issues.
@ -223,6 +223,6 @@ Current `maintainers <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-Yajo| |maintainer-Tardo| |maintainer-SplashS| |maintainer-Yajo| |maintainer-Tardo| |maintainer-SplashS|
This module is part of the `OCA/web <https://github.com/OCA/web/tree/15.0/web_responsive>`_ project on GitHub. This module is part of the `OCA/web <https://github.com/OCA/web/tree/16.0/web_responsive>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@ -1 +0,0 @@
from . import models

View File

@ -2,52 +2,47 @@
# Copyright 2017-2018 Tecnativa - Jairo Llopis # Copyright 2017-2018 Tecnativa - Jairo Llopis
# Copyright 2018-2019 Tecnativa - Alexandre Díaz # Copyright 2018-2019 Tecnativa - Alexandre Díaz
# Copyright 2021 ITerra - Sergey Shebanin # Copyright 2021 ITerra - Sergey Shebanin
# Copyright 2023 Onestein - Anjeel Haria
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). # License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
{ {
"name": "Web Responsive", "name": "Web Responsive",
"summary": "Responsive web client, community-supported", "summary": "Responsive web client, community-supported",
"version": "15.0.1.1.6", "version": "16.0.1.0.0",
"category": "Website", "category": "Website",
"website": "https://github.com/OCA/web", "website": "https://github.com/OCA/web",
"author": "LasLabs, Tecnativa, ITerra, " "Odoo Community Association (OCA)", "author": "LasLabs, Tecnativa, ITerra, Onestein, "
"Odoo Community Association (OCA)",
"license": "LGPL-3", "license": "LGPL-3",
"installable": True, "installable": True,
"depends": ["web", "mail"], "depends": ["web", "mail"],
"development_status": "Production/Stable", "development_status": "Production/Stable",
"maintainers": ["Yajo", "Tardo", "SplashS"], "maintainers": ["Yajo", "Tardo", "SplashS"],
"excludes": ["web_enterprise"], "excludes": ["web_enterprise"],
"data": ["views/res_users.xml", "views/web.xml"], "data": ["views/web.xml"],
"assets": { "assets": {
"web.assets_frontend": [
"/web_responsive/static/src/legacy/js/website_apps_menu.js",
"/web_responsive/static/src/legacy/scss/website_apps_menu.scss",
],
"web.assets_backend": [ "web.assets_backend": [
"/web_responsive/static/src/views/form/form_controller.esm.js",
"/web_responsive/static/src/legacy/scss/web_responsive.scss", "/web_responsive/static/src/legacy/scss/web_responsive.scss",
"/web_responsive/static/src/legacy/js/web_responsive.js", "/web_responsive/static/src/legacy/js/web_responsive.js",
"/web_responsive/static/src/legacy/scss/kanban_view_mobile.scss",
"/web_responsive/static/src/legacy/js/kanban_renderer_mobile.js",
"/web_responsive/static/src/components/ui_context.esm.js", "/web_responsive/static/src/components/ui_context.esm.js",
"/web_responsive/static/src/components/apps_menu/apps_menu.scss", "/web_responsive/static/src/components/apps_menu/apps_menu.scss",
"/web_responsive/static/src/components/apps_menu/apps_menu.esm.js", "/web_responsive/static/src/components/apps_menu/apps_menu.esm.js",
"/web_responsive/static/src/components/navbar/main_navbar.scss",
"/web_responsive/static/src/components/control_panel/control_panel.scss", "/web_responsive/static/src/components/control_panel/control_panel.scss",
"/web_responsive/static/src/components/control_panel/control_panel.esm.js", "/web_responsive/static/src/components/control_panel/control_panel.esm.js",
"/web_responsive/static/src/components/search_panel/search_panel.scss", "/web_responsive/static/src/components/search_panel/search_panel.scss",
"/web_responsive/static/src/components/search_panel/search_panel.esm.js", "/web_responsive/static/src/components/search_panel/search_panel.esm.js",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.scss",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.esm.js",
"/web_responsive/static/src/components/hotkey/hotkey.scss", "/web_responsive/static/src/components/hotkey/hotkey.scss",
],
"web.assets_qweb": [
"/web_responsive/static/src/legacy/xml/form_buttons.xml", "/web_responsive/static/src/legacy/xml/form_buttons.xml",
"/web_responsive/static/src/components/apps_menu/apps_menu.xml", "/web_responsive/static/src/components/apps_menu/apps_menu.xml",
"/web_responsive/static/src/components/control_panel/control_panel.xml", "/web_responsive/static/src/components/control_panel/control_panel.xml",
"/web_responsive/static/src/components/navbar/main_navbar.xml",
"/web_responsive/static/src/components/search_panel/search_panel.xml", "/web_responsive/static/src/components/search_panel/search_panel.xml",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.xml",
"/web_responsive/static/src/components/hotkey/hotkey.xml", "/web_responsive/static/src/components/hotkey/hotkey.xml",
"/web_responsive/static/src/components/chatter_topbar/chatter_topbar.esm.js",
"/web_responsive/static/src/components/chatter_topbar/chatter_topbar.xml",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.scss",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.esm.js",
"/web_responsive/static/src/components/attachment_viewer/attachment_viewer.xml",
], ],
"web.assets_tests": [ "web.assets_tests": [
"/web_responsive/static/tests/test_patch.js", "/web_responsive/static/tests/test_patch.js",

View File

@ -1 +0,0 @@
from . import res_users

View File

@ -1,26 +0,0 @@
# Copyright 2018-2019 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ResUsers(models.Model):
_inherit = "res.users"
chatter_position = fields.Selection(
[("normal", "Normal"), ("sided", "Sided")],
default="sided",
)
"""Override to add access rights.
Access rights are disabled by default, but allowed on some specific
fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
"""
@property
def SELF_READABLE_FIELDS(self):
return super().SELF_READABLE_FIELDS + ["chatter_position"]
@property
def SELF_WRITEABLE_FIELDS(self):
return super().SELF_WRITEABLE_FIELDS + ["chatter_position"]

View File

@ -1,6 +1,8 @@
* Dave Lasley <dave@laslabs.com> * Dave Lasley <dave@laslabs.com>
* Jairo Llopis <jairo.llopis@tecnativa.com> * Jairo Llopis <jairo.llopis@tecnativa.com>
* Dennis Sluijk <d.sluijk@onestein.nl> * `Onestein <https://www.onestein.nl>`_:
* Dennis Sluijk <d.sluijk@onestein.nl>
* Anjeel Haria
* Sergio Teruel <sergio.teruel@tecnativa.com> * Sergio Teruel <sergio.teruel@tecnativa.com>
* Alexandre Díaz <dev@redneboa.es> * Alexandre Díaz <dev@redneboa.es>
* Mathias Markl <mathias.markl@mukit.at> * Mathias Markl <mathias.markl@mukit.at>

View File

@ -22,42 +22,16 @@ This module adds responsiveness to web backend.
.. image:: ../static/img/listview.gif .. image:: ../static/img/listview.gif
* Increase the size of the labels in extra large screens
.. image:: ../static/img/label_size_small.png
.. image:: ../static/img/label_size_large.png
**Features for mobile**: **Features for mobile**:
* App-specific submenus are shown on full screen when toggling them from the
"hamburger" menu
.. image:: ../static/img/hamburger.gif
* User-specific submenus are shown on full screen when toggling them from the
"avatar" menu
.. image:: ../static/img/usermenu.gif
* View type picker dropdown displays comfortably * View type picker dropdown displays comfortably
.. image:: ../static/img/viewtype.gif .. image:: ../static/img/viewtype.gif
* Top app bar is always visible, but the control panel is hidden when * Control panel buttons use icons to save space.
scrolling down, to save some valuable vertical space
.. image:: ../static/img/navbar.gif
* Form status bar action and status buttons are collapsed in dropdowns.
Other control panel buttons use icons to save space.
.. image:: ../static/img/form_buttons.gif .. image:: ../static/img/form_buttons.gif
* Breadcrumbs navigation is collapsed with a "back arrow" button.
.. image:: ../static/img/breadcrumbs.gif
* Search panel is collapsed to mobile version on small screens. * Search panel is collapsed to mobile version on small screens.
.. image:: ../static/img/search_panel.gif .. image:: ../static/img/search_panel.gif
@ -66,22 +40,6 @@ This module adds responsiveness to web backend.
.. image:: ../static/img/chatter.gif .. image:: ../static/img/chatter.gif
* Scrollable dropdowns
.. image:: ../static/img/dropdown_scroll.gif
* Kanban interface adopted to mobile
.. image:: ../static/img/kanban.gif
* Calendar interface adopted to mobile
.. image:: ../static/img/calendar.gif
* Interface is adapted dynamically on device rotation
.. image:: ../static/img/device_rotation.gif
* Big inputs on form in edit mode * Big inputs on form in edit mode
**Features for desktop computers**: **Features for desktop computers**:
@ -103,15 +61,7 @@ This module adds responsiveness to web backend.
.. image:: ../static/img/formview.gif .. image:: ../static/img/formview.gif
* Set chatter on the side of the screen, optional per user * When the chatter is on the side part, the document viewer fills that
.. image:: ../static/img/chatter_sided.gif
* Sticky chatter topbar
.. image:: ../static/img/chatter_topbar.gif
* When the chatter is configured on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button. width preview clicking on the new maximize button.

View File

@ -367,71 +367,40 @@ ul.auto-toc {
!! This file is generated by oca-gen-addon-readme !! !! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !! !! 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/15.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-15-0/web-15-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/15.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/16.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-16-0/web-16-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/16.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>This module adds responsiveness to web backend.</p>
<p><strong>Features for all devices</strong>:</p> <p><strong>Features for all devices</strong>:</p>
<ul> <ul>
<li><p class="first">New navigation with the fullscreen app menu</p> <li><p class="first">New navigation with the fullscreen app menu</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appmenu.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appmenu.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appmenu.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appmenu.gif" />
</li> </li>
<li><p class="first">Quick menu search inside the app menu</p> <li><p class="first">Quick menu search inside the app menu</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif" />
</li> </li>
<li><p class="first">Sticky header &amp; footer in list view</p> <li><p class="first">Sticky header &amp; footer in list view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif" />
</li> </li>
<li><p class="first">Sticky statusbar in form view</p> <li><p class="first">Sticky statusbar in form view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif" />
</li> </li>
<li><p class="first">Bigger checkboxes in list view</p> <li><p class="first">Bigger checkboxes in list view</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/listview.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/listview.gif" />
</li>
<li><p class="first">Increase the size of the labels in extra large screens</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_small.png" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_small.png" />
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_large.png" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/label_size_large.png" />
</li> </li>
</ul> </ul>
<p><strong>Features for mobile</strong>:</p> <p><strong>Features for mobile</strong>:</p>
<ul> <ul>
<li><p class="first">App-specific submenus are shown on full screen when toggling them from the
“hamburger” menu</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/hamburger.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/hamburger.gif" />
</li>
<li><p class="first">User-specific submenus are shown on full screen when toggling them from the
“avatar” menu</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/usermenu.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/usermenu.gif" />
</li>
<li><p class="first">View type picker dropdown displays comfortably</p> <li><p class="first">View type picker dropdown displays comfortably</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/viewtype.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/viewtype.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/viewtype.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/viewtype.gif" />
</li> </li>
<li><p class="first">Top app bar is always visible, but the control panel is hidden when <li><p class="first">Control panel buttons use icons to save space.</p>
scrolling down, to save some valuable vertical space</p> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/form_buttons.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/form_buttons.gif" />
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/navbar.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/navbar.gif" />
</li>
<li><p class="first">Form status bar action and status buttons are collapsed in dropdowns.
Other control panel buttons use icons to save space.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/form_buttons.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/form_buttons.gif" />
</li>
<li><p class="first">Breadcrumbs navigation is collapsed with a “back arrow” button.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/breadcrumbs.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/breadcrumbs.gif" />
</li> </li>
<li><p class="first">Search panel is collapsed to mobile version on small screens.</p> <li><p class="first">Search panel is collapsed to mobile version on small screens.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/search_panel.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/search_panel.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/search_panel.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/search_panel.gif" />
</li> </li>
<li><p class="first">Followers and send button is displayed on mobile. Avatar is hidden.</p> <li><p class="first">Followers and send button is displayed on mobile. Avatar is hidden.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter.gif" />
</li>
<li><p class="first">Scrollable dropdowns</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/dropdown_scroll.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/dropdown_scroll.gif" />
</li>
<li><p class="first">Kanban interface adopted to mobile</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/kanban.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/kanban.gif" />
</li>
<li><p class="first">Calendar interface adopted to mobile</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/calendar.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/calendar.gif" />
</li>
<li><p class="first">Interface is adapted dynamically on device rotation</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/device_rotation.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/device_rotation.gif" />
</li> </li>
<li><p class="first">Big inputs on form in edit mode</p> <li><p class="first">Big inputs on form in edit mode</p>
</li> </li>
@ -444,21 +413,18 @@ just <cite>Alt + [NUM]</cite> to avoid conflict with Firefox Tab switching.
Standard Odoo keyboard hotkeys changed to be more intuitive or Standard Odoo keyboard hotkeys changed to be more intuitive or
accessible by fingers of one hand. accessible by fingers of one hand.
F.x. <cite>Alt + S</cite> for <cite>Save</cite></p> F.x. <cite>Alt + S</cite> for <cite>Save</cite></p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/shortcuts.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/shortcuts.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/shortcuts.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/shortcuts.gif" />
</li> </li>
<li><p class="first">Autofocus on search menu box when opening the app menu</p> <li><p class="first">Autofocus on search menu box when opening the app menu</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/appsearch.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/appsearch.gif" />
</li> </li>
<li><p class="first">Full width form sheets</p> <li><p class="first">Full width form sheets</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/formview.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/formview.gif" />
</li>
<li><p class="first">Set chatter on the side of the screen, optional per user</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_sided.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_sided.gif" />
</li> </li>
<li><p class="first">Sticky chatter topbar</p> <li><p class="first">Sticky chatter topbar</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_topbar.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/chatter_topbar.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter_topbar.gif" src="https://raw.githubusercontent.com/OCA/web/16.0/web_responsive/static/img/chatter_topbar.gif" />
</li> </li>
<li><p class="first">When the chatter is configured on the side part, the document viewer fills that <li><p class="first">When the chatter is on the side part, the document viewer fills that
part for side-by-side reading instead of full screen. You can still put it on full part for side-by-side reading instead of full screen. You can still put it on full
width preview clicking on the new maximize button.</p> width preview clicking on the new maximize button.</p>
<img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/document_viewer.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/document_viewer.gif" /> <img alt="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/document_viewer.gif" src="https://raw.githubusercontent.com/OCA/web/15.0/web_responsive/static/img/document_viewer.gif" />
@ -499,7 +465,7 @@ width preview clicking on the new maximize button.</p>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/web/issues">GitHub Issues</a>. <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. 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 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:%2015.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:%2016.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> <p>Do not contact contributors directly about support or help with technical issues.</p>
</div> </div>
<div class="section" id="credits"> <div class="section" id="credits">
@ -518,6 +484,7 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li> <li>Dave Lasley &lt;<a class="reference external" href="mailto:dave&#64;laslabs.com">dave&#64;laslabs.com</a>&gt;</li>
<li>Jairo Llopis &lt;<a class="reference external" href="mailto:jairo.llopis&#64;tecnativa.com">jairo.llopis&#64;tecnativa.com</a>&gt;</li> <li>Jairo Llopis &lt;<a class="reference external" href="mailto:jairo.llopis&#64;tecnativa.com">jairo.llopis&#64;tecnativa.com</a>&gt;</li>
<li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</li> <li>Dennis Sluijk &lt;<a class="reference external" href="mailto:d.sluijk&#64;onestein.nl">d.sluijk&#64;onestein.nl</a>&gt;</li>
<li>Anjeel Haria &lt;<a class="reference external" href="mailto:anjeel_bv&#64;onestein.nl">anjeel_bv&#64;onestein.nl</a>&gt;</li>
<li>Sergio Teruel &lt;<a class="reference external" href="mailto:sergio.teruel&#64;tecnativa.com">sergio.teruel&#64;tecnativa.com</a>&gt;</li> <li>Sergio Teruel &lt;<a class="reference external" href="mailto:sergio.teruel&#64;tecnativa.com">sergio.teruel&#64;tecnativa.com</a>&gt;</li>
<li>Alexandre Díaz &lt;<a class="reference external" href="mailto:dev&#64;redneboa.es">dev&#64;redneboa.es</a>&gt;</li> <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>Mathias Markl &lt;<a class="reference external" href="mailto:mathias.markl&#64;mukit.at">mathias.markl&#64;mukit.at</a>&gt;</li>
@ -534,7 +501,7 @@ mission is to support the collaborative development of Odoo features and
promote its widespread use.</p> promote its widespread use.</p>
<p>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainers</a>:</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> <a class="reference external" href="https://github.com/SplashS"><img alt="SplashS" src="https://github.com/SplashS.png?size=40px" /></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> <a class="reference external" href="https://github.com/SplashS"><img alt="SplashS" src="https://github.com/SplashS.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/web/tree/15.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/16.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> <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>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -1,6 +1,7 @@
/** @odoo-module **/ /** @odoo-module **/
/* Copyright 2018 Tecnativa - Jairo Llopis /* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin * Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {NavBar} from "@web/webclient/navbar/navbar"; import {NavBar} from "@web/webclient/navbar/navbar";
@ -11,16 +12,16 @@ import {debounce} from "@web/core/utils/timing";
import {fuzzyLookup} from "@web/core/utils/search"; import {fuzzyLookup} from "@web/core/utils/search";
import {WebClient} from "@web/webclient/webclient"; import {WebClient} from "@web/webclient/webclient";
import {patch} from "web.utils"; import {patch} from "web.utils";
import {escapeRegExp} from "@web/core/utils/strings";
const {Component} = owl; const {Component, useState, onPatched, onWillPatch} = owl;
const {useState, useRef} = owl.hooks;
// Patch WebClient to show AppsMenu instead of default app // Patch WebClient to show AppsMenu instead of default app
patch(WebClient.prototype, "web_responsive.DefaultAppsMenu", { patch(WebClient.prototype, "web_responsive.DefaultAppsMenu", {
setup() { setup() {
this._super(); this._super();
useBus(this.env.bus, "APPS_MENU:STATE_CHANGED", (payload) => { useBus(this.env.bus, "APPS_MENU:STATE_CHANGED", ({detail: state}) => {
this.el.classList.toggle("o_apps_menu_opened", payload); document.body.classList.toggle("o_apps_menu_opened", state);
}); });
}, },
}); });
@ -32,16 +33,96 @@ export class AppsMenu extends Component {
setup() { setup() {
super.setup(); super.setup();
this.state = useState({open: false}); this.state = useState({open: false});
this.menuService = useService("menu");
useBus(this.env.bus, "ACTION_MANAGER:UI-UPDATED", () => { useBus(this.env.bus, "ACTION_MANAGER:UI-UPDATED", () => {
this.setState(false); this.setOpenState(false, false);
}); });
useBus(this.env.bus, "APPS_MENU:CLOSE", () => { this._setupKeyNavigation();
this.setState(false); }
setOpenState(open_state, from_home_menu_click) {
this.state.open = open_state;
// Load home page with proper systray when opening it from website
if (from_home_menu_click) {
var currentapp = this.menuService.getCurrentApp();
if (currentapp && currentapp.name == "Website") {
if (window.location.pathname != "/web") {
const icon = $(
document.querySelector(".o_navbar_apps_menu button > i")
);
icon.removeClass("fa fa-th-large").append(
$("<span/>", {class: "fa fa-spin fa-spinner"})
);
}
window.location.href = "/web#home";
} else {
this.env.bus.trigger("APPS_MENU:STATE_CHANGED", open_state);
}
} else {
this.env.bus.trigger("APPS_MENU:STATE_CHANGED", open_state);
}
}
/**
* Setup navigation among app menus
*/
_setupKeyNavigation() {
const repeatable = {
allowRepeat: true,
};
useHotkey(
"ArrowRight",
() => {
this._onWindowKeydown("next");
},
repeatable
);
useHotkey(
"ArrowLeft",
() => {
this._onWindowKeydown("prev");
},
repeatable
);
useHotkey(
"ArrowDown",
() => {
this._onWindowKeydown("next");
},
repeatable
);
useHotkey(
"ArrowUp",
() => {
this._onWindowKeydown("prev");
},
repeatable
);
useHotkey("Escape", () => {
this.env.bus.trigger("ACTION_MANAGER:UI-UPDATED");
}); });
} }
setState(state) {
this.state.open = state; _onWindowKeydown(direction) {
this.env.bus.trigger("APPS_MENU:STATE_CHANGED", state); const focusableInputElements = document.querySelectorAll(`.o_app`);
if (focusableInputElements.length) {
const focusable = [...focusableInputElements];
const index = focusable.indexOf(document.activeElement);
let nextIndex = 0;
if (direction == "prev" && index >= 0) {
if (index > 0) {
nextIndex = index - 1;
} else {
nextIndex = focusable.length - 1;
}
} else if (direction == "next") {
if (index + 1 < focusable.length) {
nextIndex = index + 1;
} else {
nextIndex = 0;
}
}
focusableInputElements[nextIndex].focus();
}
} }
} }
@ -86,6 +167,16 @@ export class AppsMenu extends Component {
*/ */
function findNames(memo, menu) { function findNames(memo, menu) {
if (menu.actionID) { if (menu.actionID) {
var result = "";
if (menu.webIconData) {
const prefix = menu.webIconData.startsWith("P")
? "data:image/svg+xml;base64,"
: "data:image/png;base64,";
result = menu.webIconData.startsWith("data:image")
? menu.webIconData
: prefix + menu.webIconData.replace(/\s/g, "");
}
menu.webIconData = result;
memo[menu.name.trim()] = menu; memo[menu.name.trim()] = menu;
} }
if (menu.childrenTree) { if (menu.childrenTree) {
@ -108,8 +199,7 @@ export class AppsMenuSearchBar extends Component {
offset: 0, offset: 0,
hasResults: false, hasResults: false,
}); });
useAutofocus({selector: "input"}); this.searchBarInput = useAutofocus({refName: "SearchBarInput"});
this.searchBarInput = useRef("SearchBarInput");
this._searchMenus = debounce(this._searchMenus, 100); this._searchMenus = debounce(this._searchMenus, 100);
// Store menu data in a format searchable by fuzzy.js // Store menu data in a format searchable by fuzzy.js
this._searchableMenus = []; this._searchableMenus = [];
@ -122,26 +212,24 @@ export class AppsMenuSearchBar extends Component {
} }
// Set up key navigation // Set up key navigation
this._setupKeyNavigation(); this._setupKeyNavigation();
} onWillPatch(() => {
// Allow looping on results
willPatch() { if (this.state.offset < 0) {
// Allow looping on results this.state.offset = this.state.results.length + this.state.offset;
if (this.state.offset < 0) { } else if (this.state.offset >= this.state.results.length) {
this.state.offset = this.state.results.length + this.state.offset; this.state.offset -= this.state.results.length;
} else if (this.state.offset >= this.state.results.length) {
this.state.offset -= this.state.results.length;
}
}
patched() {
// Scroll to selected element on keyboard navigation
if (this.state.results.length) {
const listElement = this.el.querySelector(".search-results");
const activeElement = this.el.querySelector(".highlight");
if (activeElement) {
scrollTo(activeElement, listElement);
} }
} });
onPatched(() => {
// Scroll to selected element on keyboard navigation
if (this.state.results.length) {
const listElement = document.querySelector(".search-results");
const activeElement = listElement.querySelector(".highlight");
if (activeElement) {
scrollTo(activeElement, listElement);
}
}
});
} }
/** /**
@ -168,48 +256,12 @@ export class AppsMenuSearchBar extends Component {
* Setup navigation among search results * Setup navigation among search results
*/ */
_setupKeyNavigation() { _setupKeyNavigation() {
const repeatable = {
allowRepeat: true,
};
useHotkey(
"ArrowDown",
() => {
this.state.offset++;
},
repeatable
);
useHotkey(
"ArrowUp",
() => {
this.state.offset--;
},
repeatable
);
useHotkey(
"Tab",
() => {
this.state.offset++;
},
repeatable
);
useHotkey(
"Shift+Tab",
() => {
this.state.offset--;
},
repeatable
);
useHotkey("Home", () => { useHotkey("Home", () => {
this.state.offset = 0; this.state.offset = 0;
}); });
useHotkey("End", () => { useHotkey("End", () => {
this.state.offset = this.state.results.length - 1; this.state.offset = this.state.results.length - 1;
}); });
useHotkey("Enter", () => {
if (this.state.results.length) {
this.el.querySelector(".highlight").click();
}
});
} }
_onKeyDown(ev) { _onKeyDown(ev) {
@ -224,9 +276,60 @@ export class AppsMenuSearchBar extends Component {
} else { } else {
this.env.bus.trigger("ACTION_MANAGER:UI-UPDATED"); this.env.bus.trigger("ACTION_MANAGER:UI-UPDATED");
} }
} else if (ev.code === "Tab") {
if (document.querySelector(".search-results")) {
ev.preventDefault();
if (event.shiftKey) {
this.state.offset--;
} else {
this.state.offset++;
}
}
} else if (ev.code === "ArrowUp") {
if (document.querySelector(".search-results")) {
ev.preventDefault();
this.state.offset--;
}
} else if (ev.code === "ArrowDown") {
if (document.querySelector(".search-results")) {
ev.preventDefault();
this.state.offset++;
}
} else if (ev.code === "Enter") {
if (this.state.results.length) {
ev.preventDefault();
document.querySelector(".search-results .highlight").click();
}
} }
} }
_splitName(name) {
const searchValue = this.searchBarInput.el.value;
if (name) {
const splitName = name.split(
new RegExp(`(${escapeRegExp(searchValue)})`, "ig")
);
return searchValue.length && splitName.length > 1 ? splitName : [name];
}
return [];
}
} }
// Patch Navbar to add proper icon for apps
patch(NavBar.prototype, "web_responsive.navbar", {
getWebIconData(menu) {
var result = "/web_responsive/static/img/default_icon_app.png";
if (menu.webIconData) {
const prefix = menu.webIconData.startsWith("P")
? "data:image/svg+xml;base64,"
: "data:image/png;base64,";
result = menu.webIconData.startsWith("data:image")
? menu.webIconData
: prefix + menu.webIconData.replace(/\s/g, "");
}
return result;
},
});
AppsMenu.template = "web_responsive.AppsMenu"; AppsMenu.template = "web_responsive.AppsMenu";
AppsMenuSearchBar.template = "web_responsive.AppsMenuSearchResults"; AppsMenuSearchBar.template = "web_responsive.AppsMenuSearchResults";
Object.assign(NavBar.components, {AppsMenu, AppsMenuSearchBar}); Object.assign(NavBar.components, {AppsMenu, AppsMenuSearchBar});

View File

@ -12,13 +12,12 @@
width: 100vw; width: 100vw;
z-index: 200; z-index: 200;
left: 0 !important; left: 0 !important;
top: $o-navbar-height !important;
} }
.o_apps_menu_opened .o_main_navbar { .o_apps_menu_opened .o_main_navbar {
.o_menu_brand, .o_menu_brand,
.o_menu_sections { .o_menu_sections {
display: none; display: none !important;
} }
} }
@ -32,7 +31,7 @@
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
} }
.dropdown-menu { .dropdown-menu-custom {
@include full-screen-dropdown(); @include full-screen-dropdown();
cursor: pointer; cursor: pointer;
background: url("../../img/home-menu-bg-overlay.svg"), background: url("../../img/home-menu-bg-overlay.svg"),
@ -62,7 +61,24 @@
} }
.o_app { .o_app {
background: none; outline: 0;
height: 100%;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
justify-content: flex-start;
white-space: normal;
color: $white !important;
padding: 15px 0 10px;
font-size: 1.25rem;
text-shadow: 1px 1px 1px rgba($black, 0.4);
border-radius: 4px;
transition: 300ms ease;
transition-property: background-color;
&:focus {
background-color: rgba($white, 0.05) !important;
}
img { img {
box-shadow: none; box-shadow: none;
margin-bottom: 5px; margin-bottom: 5px;
@ -70,31 +86,10 @@
transition-property: box-shadow, transform; transition-property: box-shadow, transform;
} }
a {
outline: 0;
height: 100%;
display: flex;
align-items: center;
text-align: center;
flex-direction: column;
justify-content: flex-start;
white-space: normal;
color: gray("white") !important;
padding: 15px 0 10px;
font-size: 1.25rem;
text-shadow: 1px 1px 1px rgba(gray("black"), 0.4);
border-radius: 4px;
transition: 300ms ease;
transition-property: background-color;
background: none;
&:focus {
background-color: rgba(gray("white"), 0.05);
}
}
&:hover img, &:hover img,
a:focus img { a:focus img {
transform: translateY(-3px); transform: translateY(-3px);
box-shadow: 0 9px 12px -4px rgba(gray("black"), 0.3); box-shadow: 0 9px 12px -4px rgba($black, 0.3);
} }
// Size depends on screen // Size depends on screen
@ -129,21 +124,20 @@
.search-input { .search-input {
display: flex; display: flex;
justify-items: middle; justify-items: center;
box-shadow: inset 0 1px 0 rgba(gray("white"), 0.1), box-shadow: inset 0 1px 0 rgba($white, 0.1), 0 1px 0 rgba($black, 0.1);
0 1px 0 rgba(gray("black"), 0.1); text-shadow: 0 1px 0 rgba($black, 0.5);
text-shadow: 0 1px 0 rgba(gray("black"), 0.5);
border-radius: 4px; border-radius: 4px;
padding: 0.4rem 0.8rem; padding: 0.4rem 0.8rem;
margin-bottom: 1rem; margin-bottom: 1rem;
background-color: rgba(gray("white"), 0.1); background-color: rgba($white, 0.1);
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
padding: 0.8rem 1.2rem; padding: 0.8rem 1.2rem;
} }
.search-icon { .search-icon {
color: gray("white"); color: $white;
font-size: 1.5rem; font-size: 1.5rem;
margin-right: 1rem; margin-right: 1rem;
padding-top: 1px; padding-top: 1px;
@ -153,19 +147,27 @@
height: 2rem; height: 2rem;
background: none; background: none;
border: none; border: none;
color: gray("white"); color: $white;
display: block; display: block;
padding: 1px 2px 2px 2px; padding: 1px 2px 2px 2px;
box-shadow: none; box-shadow: none;
&::placeholder { &::placeholder {
color: gray("white"); color: $white;
opacity: 0.5; opacity: 0.5;
} }
} }
} }
// Allow to scroll only on results, keeping static search box above // Allow to scroll only on results, keeping static search box above
.search-results { .search-results {
.text-ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.text-primary {
color: red !important;
}
margin-top: 1rem; margin-top: 1rem;
max-height: calc(100vh - #{$o-navbar-height} - 8rem) !important; max-height: calc(100vh - #{$o-navbar-height} - 8rem) !important;
overflow: auto; overflow: auto;
@ -177,7 +179,7 @@
background-position: left; background-position: left;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain; background-size: contain;
color: gray("white"); color: $white;
cursor: pointer; cursor: pointer;
line-height: 2.5rem; line-height: 2.5rem;
padding-left: 3.5rem; padding-left: 3.5rem;
@ -185,7 +187,7 @@
font-weight: 100; font-weight: 100;
&.highlight, &.highlight,
&:hover { &:hover {
background-color: rgba(gray("black"), 0.11); background-color: rgba($black, 0.11);
} }
b { b {
font-weight: 700; font-weight: 700;
@ -194,3 +196,10 @@
} }
} }
} }
.dropdown-menu-custom {
max-height: 70vh;
overflow: auto;
background-clip: border-box;
box-shadow: $o-dropdown-box-shadow;
}

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2018 Tecnativa - Jairo Llopis <!-- Copyright 2018 Tecnativa - Jairo Llopis
Copyright 2021 ITerra - Sergey Shebanin Copyright 2021 ITerra - Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). --> License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates> <templates>
<t t-inherit="web.NavBar.AppsMenu" t-inherit-mode="extension" owl="1"> <t t-inherit="web.NavBar.AppsMenu" t-inherit-mode="extension" owl="1">
@ -8,23 +9,22 @@
<!-- Same hotkey as in EE --> <!-- Same hotkey as in EE -->
<AppsMenu> <AppsMenu>
<AppsMenuSearchBar /> <AppsMenuSearchBar />
<MenuItem <DropdownItem
t-foreach="apps" t-foreach="apps"
t-as="app" t-as="app"
t-key="app.id" t-key="app.id"
class="o_app" class="'o_app'"
t-att-class="{ o_dropdown_active: menuService.getCurrentApp() === app }" dataset="{ menuXmlid: app.xmlid, section: app.id }"
payload="app" href="getMenuItemHref(app)"
onSelected="() => this.onNavBarDropdownItemSelection(app)"
> >
<a t-att-href="getMenuItemHref(app)">
<img <img
class="o-app-icon" class="o-app-icon"
draggable="false" draggable="false"
t-attf-src="data:image/png;base64,{{app.webIconData}}" t-att-src="getWebIconData(app)"
/> />
<div t-esc="app.name" /> <div t-esc="app.name" />
</a> </DropdownItem>
</MenuItem>
</AppsMenu> </AppsMenu>
</xpath> </xpath>
</t> </t>
@ -35,16 +35,11 @@
class="dropdown-toggle" class="dropdown-toggle"
title="Home Menu" title="Home Menu"
data-hotkey="a" data-hotkey="a"
t-on-click.stop="setState(!state.open)" t-on-click.stop="() => this.setOpenState(!state.open,true)"
> >
<i class="fa fa-th-large" /> <i class="fa fa-th-large" />
</button> </button>
<div <div t-if="state.open" class="dropdown-menu-custom">
t-if="state.open"
class="dropdown-menu"
style="top: 46px; left: 0px;"
t-transition="fade"
>
<t t-slot="default" /> <t t-slot="default" />
</div> </div>
</div> </div>
@ -70,17 +65,31 @@
/> />
</div> </div>
<div t-if="state.results.length" class="search-results"> <div t-if="state.results.length" class="search-results">
<t t-foreach="state.results" t-as="result"> <t t-foreach="state.results" t-as="result" t-key="result">
<t t-set="menu" t-value="_menuInfo(result)" /> <t t-set="menu" t-value="_menuInfo(result)" />
<a <a
t-attf-class="search-result {{result_index == state.offset ? 'highlight' : ''}}" t-attf-class="search-result {{result_index == state.offset ? 'highlight' : ''}}"
t-att-style="menu.webIconData ? &quot;background-image:url('data:image/png;base64,&quot; + menu.webIconData + &quot;')&quot; : ''" t-att-style="menu.webIconData ? &quot;background-image:url(&quot; + menu.webIconData + &quot;);background-size:4%&quot; : ''"
t-attf-href="#menu_id={{menu.id}}&amp;action={{menu.actionID}}" t-attf-href="#menu_id={{menu.id}}&amp;action={{menu.actionID}}"
t-att-data-menu-id="menu.id" t-att-data-menu-id="menu.id"
t-att-data-action-id="menu.actionID" t-att-data-action-id="menu.actionID"
draggable="false" draggable="false"
t-esc="result" >
/> <span class="text-ellipsis" t-att-title="result.name">
<t
t-foreach="_splitName(result)"
t-as="name"
t-key="name_index"
>
<b
t-if="name_index % 2"
t-out="name"
style="text-primary"
/>
<t t-else="" t-out="name" />
</t>
</span>
</a>
</t> </t>
</div> </div>
</div> </div>

View File

@ -1,11 +1,12 @@
/** @odoo-module **/ /** @odoo-module **/
/* Copyright 2021 ITerra - Sergey Shebanin /* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {AttachmentViewer} from "@mail/components/attachment_viewer/attachment_viewer"; import {AttachmentViewer} from "@mail/components/attachment_viewer/attachment_viewer";
import {patch} from "web.utils"; import {patch} from "web.utils";
import {registerPatch} from "@mail/model/model_core";
const {useState} = owl.hooks; const {useState} = owl;
// Patch attachment viewer to add min/max buttons capability // Patch attachment viewer to add min/max buttons capability
patch(AttachmentViewer.prototype, "web_responsive.AttachmentViewer", { patch(AttachmentViewer.prototype, "web_responsive.AttachmentViewer", {
@ -15,8 +16,22 @@ patch(AttachmentViewer.prototype, "web_responsive.AttachmentViewer", {
maximized: false, maximized: false,
}); });
}, },
// Disable auto-close to allow to use form in edit mode. });
isCloseable() {
return false; registerPatch({
name: "Dialog",
fields: {
isCloseable: {
compute() {
if (this.attachmentViewer) {
/**
* Prevent closing the dialog when clicking on the mask when the user is
* currently dragging the image.
*/
return false;
}
return this._super();
},
},
}, },
}); });

View File

@ -3,10 +3,12 @@
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
// Attachment Viewer // Attachment Viewer
.o_web_client.o_chatter_position_sided .o_DialogManager_dialog { .o_web_client .o_DialogManager_dialog {
/* Show sided viewer on large screens */ /* Show sided viewer on large screens */
@include media-breakpoint-up(lg) { @media (min-width: 1533px) {
position: static; &:not(:has(.o_AttachmentDeleteConfirm)) {
position: static;
}
.o_AttachmentViewer_main { .o_AttachmentViewer_main {
padding-bottom: 20px; padding-bottom: 20px;
} }
@ -22,7 +24,7 @@
width: $chatter_zone_width; width: $chatter_zone_width;
&.o_AttachmentViewer_maximized { &.o_AttachmentViewer_maximized {
width: 100%; width: 100% !important;
} }
/* Show/Hide control buttons (next, prev, etc..) */ /* Show/Hide control buttons (next, prev, etc..) */
@ -39,18 +41,21 @@
} }
} }
} }
@include media-breakpoint-down(md) { @media (max-width: 1533px) {
.o_AttachmentViewer_headerItemButtonMinimize, .o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize { .o_AttachmentViewer_headerItemButtonMaximize {
display: none; display: none !important;
} }
} }
} }
/* Attachment Viewer Max/Min buttons only are useful in sided mode */ /* Attachment Viewer Max/Min buttons only are useful in sided mode */
.o_web_client:not(.o_chatter_position_sided) { .o_FormRenderer_chatterContainer:not(.o-aside) {
.o_AttachmentViewer_headerItemButtonMinimize, .o_AttachmentViewer_headerItemButtonMinimize,
.o_AttachmentViewer_headerItemButtonMaximize { .o_AttachmentViewer_headerItemButtonMaximize {
display: none; display: none !important;
} }
} }
.o_apps_menu_opened .o_AttachmentViewer {
display: none !important;
}

View File

@ -16,8 +16,8 @@
> >
<div <div
t-if="!state.maximized" t-if="!state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMaximize" class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMaximize d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer"
t-on-click="state.maximized=true" t-on-click="() => { state.maximized = true }"
role="button" role="button"
title="Maximize" title="Maximize"
aria-label="Maximize" aria-label="Maximize"
@ -26,8 +26,8 @@
</div> </div>
<div <div
t-if="state.maximized" t-if="state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMinimize" class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMinimize d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer"
t-on-click="state.maximized=false" t-on-click="() => { state.maximized = false }"
role="button" role="button"
title="Minimize" title="Minimize"
aria-label="Minimize" aria-label="Minimize"

View File

@ -0,0 +1,15 @@
/** @odoo-module **/
/* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {ChatterTopbar} from "@mail/components/chatter_topbar/chatter_topbar";
import {deviceContext} from "@web_responsive/components/ui_context.esm";
import {patch} from "web.utils";
// Patch chatter topbar to add ui device context
patch(ChatterTopbar.prototype, "web_responsive.ChatterTopbar", {
setup() {
this._super();
this.ui = deviceContext;
},
});

View File

@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<templates xml:space="preserve">
<!-- Modifying the ChatterTopBar for Mobile View -->
<t
t-name="web.Responsivemail.ChatterTopbar"
t-inherit="mail.ChatterTopbar"
owl="1"
t-inherit-mode="extension"
>
<xpath expr="//div[contains(@class, 'o_ChatterTopbar')]" position="replace">
<t t-if="ui.isSmall">
<div
class="o_ChatterTopbar_rightSection d-flex border-bottom"
style="max-height:45%"
>
<button
t-if="chatterTopbar.chatter.thread.allAttachments.length === 0"
class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-light btn-primary"
type="button"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasWriteAccess"
t-on-click="chatterTopbar.chatter.onClickButtonAddAttachments"
style="width:41%"
>
<i
class="fa fa-paperclip fa-lg me-1"
role="img"
aria-label="Attachments"
/>
<t t-if="chatterTopbar.chatter.isShowingAttachmentsLoading">
<i
class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin"
aria-label="Attachment counter loading..."
/>
</t>
</button>
<button
t-if="chatterTopbar.chatter.thread.allAttachments.length > 0"
class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-light btn-primary"
type="button"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasReadAccess"
t-att-aria-expanded="chatterTopbar.chatter.attachmentBoxView ? 'true' : 'false'"
t-on-click="chatterTopbar.chatter.onClickButtonToggleAttachments"
style="width:41%"
>
<i
class="fa fa-paperclip fa-lg me-1"
role="img"
aria-label="Attachments"
/>
<t t-if="!chatterTopbar.chatter.isShowingAttachmentsLoading">
<span
class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount"
t-esc="chatterTopbar.attachmentButtonText"
/>
</t>
<t t-if="chatterTopbar.chatter.isShowingAttachmentsLoading">
<i
class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin"
aria-label="Attachment counter loading..."
/>
</t>
</button>
<t
t-if="chatterTopbar.chatter.hasFollowers and chatterTopbar.chatter.thread"
>
<FollowerListMenu
className="'o_ChatterTopbar_followerListMenu w-26'"
record="chatterTopbar.chatter.followerListMenuView"
/>
<t t-if="chatterTopbar.chatter.followButtonView">
<FollowButton
className="'o_ChatterTopbar_followButton'"
record="chatterTopbar.chatter.followButtonView"
/>
</t>
</t>
</div>
</t>
<div
class="o_ChatterTopbar justify-content-between d-flex"
t-attf-class="{{ className }}"
t-ref="root"
>
<div
class="o_ChatterTopbar_actions flex-fill d-flex border-transparent"
>
<div
class="o_ChatterTopbar_controllers d-flex pe-2"
t-if="chatterTopbar.chatter.threadView"
>
<button
class="o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage btn text-nowrap me-2"
type="button"
t-att-class="{
'o-active btn-odoo': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog,
'btn-odoo': !chatterTopbar.chatter.composerView,
'btn-light': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog,
}"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasWriteAccess"
data-hotkey="m"
t-on-click="chatterTopbar.chatter.onClickSendMessage"
>
Send message
</button>
<button
class="o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote btn text-nowrap"
type="button"
t-att-class="{
'o-active btn-odoo': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog,
'btn-light': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog or !chatterTopbar.chatter.composerView,
}"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasWriteAccess"
t-on-click="chatterTopbar.chatter.onClickLogNote"
data-hotkey="shift+m"
>
Log note
</button>
</div>
<div
class="o_ChatterTopbar_tools position-relative d-flex flex-grow-1 border-bottom"
t-att-class="{
'border-start ps-2': chatterTopbar.chatter.hasActivities,
}"
>
<t t-if="chatterTopbar.chatter.hasActivities">
<button
class="o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity btn btn-light text-nowrap"
type="button"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasWriteAccess"
t-on-click="chatterTopbar.chatter.onClickScheduleActivity"
data-hotkey="shift+a"
>
<i class="fa fa-clock-o me-1" />
<span>Activities</span>
</button>
</t>
<div
class="flex-grow-1 border-start pe-2"
t-att-class="{
'ms-2': chatterTopbar.chatter.hasActivities,
}"
/>
<t t-if="!ui.isSmall">
<div
class="o_ChatterTopbar_rightSection flex-grow-1 flex-shrink-0 justify-content-end d-flex"
>
<button
t-if="chatterTopbar.chatter.thread.allAttachments.length === 0"
class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-light btn-primary"
type="button"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasWriteAccess"
t-on-click="chatterTopbar.chatter.onClickButtonAddAttachments"
>
<i
class="fa fa-paperclip fa-lg me-1"
role="img"
aria-label="Attachments"
/>
<t
t-if="chatterTopbar.chatter.isShowingAttachmentsLoading"
>
<i
class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin"
aria-label="Attachment counter loading..."
/>
</t>
</button>
<button
t-if="chatterTopbar.chatter.thread.allAttachments.length > 0"
class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-light btn-primary"
type="button"
t-att-disabled="!chatterTopbar.chatter.isTemporary and !chatterTopbar.chatter.hasReadAccess"
t-att-aria-expanded="chatterTopbar.chatter.attachmentBoxView ? 'true' : 'false'"
t-on-click="chatterTopbar.chatter.onClickButtonToggleAttachments"
>
<i
class="fa fa-paperclip fa-lg me-1"
role="img"
aria-label="Attachments"
/>
<t
t-if="!chatterTopbar.chatter.isShowingAttachmentsLoading"
>
<span
class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount"
t-esc="chatterTopbar.attachmentButtonText"
/>
</t>
<t
t-if="chatterTopbar.chatter.isShowingAttachmentsLoading"
>
<i
class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin"
aria-label="Attachment counter loading..."
/>
</t>
</button>
<t
t-if="chatterTopbar.chatter.hasFollowers and chatterTopbar.chatter.thread"
>
<FollowerListMenu
className="'o_ChatterTopbar_followerListMenu'"
record="chatterTopbar.chatter.followerListMenuView"
/>
<t t-if="chatterTopbar.chatter.followButtonView">
<FollowButton
className="'o_ChatterTopbar_followButton'"
record="chatterTopbar.chatter.followButtonView"
/>
</t>
</t>
</div>
</t>
</div>
</div>
</div>
</xpath>
</t>
</templates>

View File

@ -1,14 +1,15 @@
/** @odoo-module **/ /** @odoo-module **/
/* Copyright 2021 ITerra - Sergey Shebanin /* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import LegacyControlPanel from "web.ControlPanel"; import LegacyControlPanel from "web.ControlPanel";
import {ControlPanel} from "@web/search/control_panel/control_panel"; import {ControlPanel} from "@web/search/control_panel/control_panel";
import {SearchBar} from "@web/search/search_bar/search_bar";
import {deviceContext} from "@web_responsive/components/ui_context.esm"; import {deviceContext} from "@web_responsive/components/ui_context.esm";
import {patch} from "web.utils"; import {patch} from "web.utils";
import {Dropdown} from "@web/core/dropdown/dropdown";
const {useState, useContext} = owl.hooks; const {useState} = owl;
// In v15.0 there are two ControlPanel's. They are mostly the same and are used in legacy and new owl views. // In v15.0 there are two ControlPanel's. They are mostly the same and are used in legacy and new owl views.
// We extend them two mostly the same way. // We extend them two mostly the same way.
@ -20,7 +21,7 @@ patch(LegacyControlPanel.prototype, "web_responsive.LegacyControlPanelMobile", {
this.state = useState({ this.state = useState({
mobileSearchMode: this.props.withBreadcrumbs ? "" : "quick", mobileSearchMode: this.props.withBreadcrumbs ? "" : "quick",
}); });
this.ui = useContext(deviceContext); this.ui = deviceContext;
}, },
setMobileSearchMode(ev) { setMobileSearchMode(ev) {
this.state.mobileSearchMode = ev.detail; this.state.mobileSearchMode = ev.detail;
@ -34,12 +35,11 @@ patch(ControlPanel.prototype, "web_responsive.ControlPanelMobile", {
this.state = useState({ this.state = useState({
mobileSearchMode: "", mobileSearchMode: "",
}); });
this.ui = useContext(deviceContext); this.ui = deviceContext;
}, },
setMobileSearchMode(ev) { setMobileSearchMode(ev) {
this.state.mobileSearchMode = ev.detail; this.state.mobileSearchMode = ev.detail;
}, },
}); });
patch(SearchBar, "web_responsive.SearchBarMobile", {
template: "web_responsive.SearchBar", Object.assign(LegacyControlPanel.components, {Dropdown});
});

View File

@ -19,12 +19,12 @@
} }
// For FULL HD devices // For FULL HD devices
@media (min-width: 1900px) { @media (min-width: 1900px) {
.o_action_manager & .o_cp_top_left, .o_cp_top_left,
.o_action_manager & .o_cp_bottom_left { .o_cp_bottom_left {
width: 60%; width: 60%;
} }
.o_action_manager & .o_cp_top_right, .o_cp_top_right,
.o_action_manager & .o_cp_bottom_right { .o_cp_bottom_right {
width: 40%; width: 40%;
} }
} }
@ -42,9 +42,6 @@
// It doesn't work on iOS Safari, but it looks similar as // It doesn't work on iOS Safari, but it looks similar as
// without this patch. With this patch it looks better for // without this patch. With this patch it looks better for
// other browsers. // other browsers.
position: sticky;
left: 0;
z-index: 3;
// Arrange buttons to use space better // Arrange buttons to use space better
.o_cp_top_left, .o_cp_top_left,
@ -53,24 +50,24 @@
} }
.o_cp_top_left { .o_cp_top_left {
flex-basis: 80%; flex-basis: 89%;
max-width: 80%; max-width: 89%;
} }
.o_cp_top_right { .o_cp_top_right {
flex-basis: 20%; flex-basis: 11%;
} }
.o_cp_bottom { .o_cp_bottom {
position: relative; // Necessary for dropdown menu positioning position: relative; // Necessary for dropdown menu positioning
display: block; display: block;
margin: 0; margin: 0;
min-height: 30px !important;
} }
.o_cp_bottom_left { .o_cp_bottom_left {
float: left; float: left;
margin: 5px 0; margin: 5px 0;
max-width: 80%;
} }
.o_cp_bottom_right { .o_cp_bottom_right {
@ -102,7 +99,6 @@
.dropdown-toggle { .dropdown-toggle {
margin: 0px 2px; margin: 0px 2px;
height: 100%; height: 100%;
padding-right: 0.5rem !important;
} }
.dropdown { .dropdown {
height: 100%; height: 100%;
@ -230,16 +226,18 @@
z-index: $zindex-modal; z-index: $zindex-modal;
overflow: auto; overflow: auto;
.o_mobile_search_header { .o_mobile_search_header {
height: 46px; background-color: var(--mobileSearch__header-bg, #{$o-brand-odoo});
margin-bottom: 10px; display: flex;
min-height: $o-navbar-height;
justify-content: space-between;
width: 100%; width: 100%;
background-color: $o-brand-odoo;
color: white; .o_mobile_search_button {
span:active { color: white;
background-color: darken($o-brand-primary, 10%);
} &:active {
span { background-color: darken($o-brand-primary, 10%);
cursor: pointer; }
} }
} }
.o_searchview_input_container { .o_searchview_input_container {
@ -266,10 +264,10 @@
line-height: 2rem; line-height: 2rem;
width: 100%; width: 100%;
margin: 15px 5px 0px 5px; margin: 15px 5px 0px 5px;
border: solid 1px darken(gray("200"), 20%); border: solid 1px darken($gray-200, 20%);
} }
.dropdown.show > .dropdown-toggle { .dropdown.show > .dropdown-toggle {
background-color: gray("200"); background-color: $gray-200;
} }
.dropdown-toggle { .dropdown-toggle {
width: 100%; width: 100%;
@ -294,7 +292,7 @@
max-height: 100%; max-height: 100%;
box-shadow: none; box-shadow: none;
border: none; border: none;
color: gray("600"); color: $gray-600;
.divider { .divider {
margin: 0px; margin: 0px;
} }

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin <!-- Copyright 2021 Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). --> License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates> <templates>
<!-- Legacy control panel templates --> <!-- Legacy control panel templates -->
@ -43,7 +44,7 @@
t-att-class="state.mobileSearchMode == 'quick' ? 'o_searchview_quick' : 'o_searchview_mobile'" t-att-class="state.mobileSearchMode == 'quick' ? 'o_searchview_quick' : 'o_searchview_mobile'"
role="search" role="search"
aria-autocomplete="list" aria-autocomplete="list"
t-on-click.self="state.mobileSearchMode = ui.isSmall ? 'quick' : ''" t-on-click.self="() => { state.mobileSearchMode = ui.isSmall ? 'quick' : '' }"
> >
<t t-if="!ui.isSmall"> <t t-if="!ui.isSmall">
<i <i
@ -59,12 +60,12 @@
<button <button
t-if="props.withBreadcrumbs" t-if="props.withBreadcrumbs"
class="btn btn-link fa fa-arrow-left" class="btn btn-link fa fa-arrow-left"
t-on-click.stop="state.mobileSearchMode = ''" t-on-click.stop="() => { state.mobileSearchMode = '' }"
/> />
<SearchBar fields="fields" /> <SearchBar fields="fields" />
<button <button
class="btn fa fa-filter" class="btn fa fa-filter"
t-on-click.stop="state.mobileSearchMode = 'full'" t-on-click.stop="() => { state.mobileSearchMode = 'full' }"
/> />
</t> </t>
<t <t
@ -74,7 +75,7 @@
<t t-if="state.mobileSearchMode == ''"> <t t-if="state.mobileSearchMode == ''">
<button <button
class="btn btn-link fa fa-search" class="btn btn-link fa fa-search"
t-on-click.stop="state.mobileSearchMode = 'quick'" t-on-click.stop="() => { state.mobileSearchMode = 'quick' }"
/> />
</t> </t>
</t> </t>
@ -97,19 +98,21 @@
<t t-name="web_responsive.LegacyMobileSearchView" owl="1"> <t t-name="web_responsive.LegacyMobileSearchView" owl="1">
<div class="o_cp_mobile_search"> <div class="o_cp_mobile_search">
<div class="o_mobile_search_header"> <div class="o_mobile_search_header">
<span <button
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16" type="button"
t-on-click.stop="state.mobileSearchMode = 'quick'" class="o_mobile_search_button btn"
t-on-click="() => state.mobileSearchMode = false"
> >
<i class="fa fa-arrow-left" /> <i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong> <strong class="ms-2">FILTER</strong>
</span> </button>
<span <button
class="float-right o_mobile_search_clear_facets mt16 mr16" type="button"
t-on-click.stop="model.dispatch('clearQuery')" class="o_mobile_search_button btn"
t-on-click="() => this.model.dispatch('clearQuery')"
> >
<t>CLEAR</t> CLEAR
</span> </button>
</div> </div>
<SearchBar fields="fields" /> <SearchBar fields="fields" />
<div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16"> <div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16">
@ -134,91 +137,13 @@
</div> </div>
<div <div
class="btn btn-primary o_mobile_search_show_result fixed-bottom" class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="state.mobileSearchMode = (props.withBreadcrumbs ? '' : 'quick')" t-on-click="() => { state.mobileSearchMode = (props.withBreadcrumbs ? '' : 'quick') }"
> >
<t>SEE RESULT</t> <t>SEE RESULT</t>
</div> </div>
</div> </div>
</t> </t>
<!-- Wowl control panel templates -->
<t t-inherit="web.ControlPanel" t-inherit-mode="extension" owl="1">
<xpath expr="//nav[hasclass('o_cp_switch_buttons')]" position="replace">
<t t-if="ui.size lt= ui.SIZES.LG">
<t
t-set="view"
t-value="env.config.viewSwitcherEntries.find((v) => v.active)"
/>
<Dropdown
position="'bottom-end'"
menuClass="'d-inline-flex o_cp_switch_buttons'"
togglerClass="'btn btn-link'"
>
<t t-set-slot="toggler">
<i
class="fa fa-lg o_switch_view"
t-attf-class="o_{{view.type}} {{view.icon}} active"
/>
</t>
<t
t-foreach="env.config.viewSwitcherEntries"
t-as="view"
t-key="view.type"
>
<button
class="btn btn-light fa o_switch_view"
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
t-att-data-tooltip="view.name"
t-on-click="onViewClicked(view.type)"
/>
</t>
</Dropdown>
</t>
<t t-else="">
<nav class="btn-group o_cp_switch_buttons">
<t
t-foreach="env.config.viewSwitcherEntries"
t-as="view"
t-key="view.type"
>
<button
class="btn btn-light fa fa-lg o_switch_view "
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
t-att-data-tooltip="view.name"
t-on-click="onViewClicked(view.type)"
/>
</t>
</nav>
</t>
</xpath>
<xpath expr="//SearchBar" position="replace">
<!-- This duplication is hack because owl has a bug https://github.com/odoo/owl/issues/949 -->
<SearchBar
t-if="state.mobileSearchMode == 'quick'"
mobileSearchMode="state.mobileSearchMode"
searchMenus="searchMenus"
t-on-set-mobile-view.stop="setMobileSearchMode"
/>
<SearchBar
t-else=""
mobileSearchMode="state.mobileSearchMode"
searchMenus="searchMenus"
t-on-set-mobile-view.stop="setMobileSearchMode"
/>
</xpath>
<xpath expr="//div[hasclass('o_cp_top_left')]" position="attributes">
<attribute
name="t-att-class"
t-translation="off"
>ui.isSmall and state.mobileSearchMode == 'quick' ? 'o_hidden' : ''</attribute>
</xpath>
<xpath expr="//div[hasclass('o_search_options')]" position="attributes">
<attribute name="t-if" t-translation="off">!ui.isSmall</attribute>
<attribute
name="t-att-class"
t-translation="off"
>ui.size == ui.SIZES.MD ? 'o_search_options_hide_labels' : ''</attribute>
</xpath>
</t>
<t t-name="web_responsive.SearchBar" owl="1"> <t t-name="web_responsive.SearchBar" owl="1">
<div> <div>
<t t-if="!env.isSmall" t-call="web.SearchBar" /> <t t-if="!env.isSmall" t-call="web.SearchBar" />
@ -228,7 +153,7 @@
<button <button
t-if="props.withBreadcrumbs" t-if="props.withBreadcrumbs"
class="btn btn-link fa fa-arrow-left" class="btn btn-link fa fa-arrow-left"
t-on-click.stop="trigger('set-mobile-view', '')" t-on-click.stop="() => this.trigger('set-mobile-view', '')"
/> />
<div class="o_searchview_input_container"> <div class="o_searchview_input_container">
<t t-call="web.SearchBar.Facets" /> <t t-call="web.SearchBar.Facets" />
@ -239,7 +164,7 @@
</div> </div>
<button <button
class="btn fa fa-filter" class="btn fa fa-filter"
t-on-click.stop="trigger('set-mobile-view', 'full')" t-on-click.stop="() => this.trigger('set-mobile-view', 'full')"
/> />
</div> </div>
</t> </t>
@ -252,7 +177,7 @@
class="o_searchview o_searchview_mobile" class="o_searchview o_searchview_mobile"
role="search" role="search"
aria-autocomplete="list" aria-autocomplete="list"
t-on-click.stop="trigger('set-mobile-view', 'quick')" t-on-click.stop="() => this.trigger('set-mobile-view', 'quick')"
> >
<button class="btn btn-link fa fa-search" /> <button class="btn btn-link fa fa-search" />
</div> </div>
@ -266,14 +191,14 @@
<div class="o_mobile_search_header"> <div class="o_mobile_search_header">
<span <span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16" class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="trigger('set-mobile-view', 'quick')" t-on-click.stop="() => this.trigger('set-mobile-view', 'quick')"
> >
<i class="fa fa-arrow-left" /> <i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong> <strong class="float-right ml8">FILTER</strong>
</span> </span>
<span <span
class="float-right o_mobile_search_clear_facets mt16 mr16" class="float-right o_mobile_search_clear_facets mt16 mr16"
t-on-click.stop="env.searchModel.clearQuery()" t-on-click.stop="() => env.searchModel.clearQuery()"
> >
<t>CLEAR</t> <t>CLEAR</t>
</span> </span>
@ -292,7 +217,7 @@
</div> </div>
<div <div
class="btn btn-primary o_mobile_search_show_result fixed-bottom" class="btn btn-primary o_mobile_search_show_result fixed-bottom"
t-on-click.stop="trigger('set-mobile-view', '')" t-on-click.stop="() => this.trigger('set-mobile-view', '')"
> >
<t>SEE RESULT</t> <t>SEE RESULT</t>
</div> </div>

View File

@ -36,36 +36,4 @@
>props.withAccessKey ? 'x' : false</attribute> >props.withAccessKey ? 'x' : false</attribute>
</xpath> </xpath>
</t> </t>
<t t-inherit="web.UserMenu.shortcutsTable" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('row')]" position="attributes">
<attribute name="class" separator=" " add="justify-content-center" />
</xpath>
<xpath expr="//div[hasclass('row')]/div" position="attributes">
<attribute name="class" />
</xpath>
<xpath expr="//span[text()='a']" position="replace">
<span class="o_key">e</span>
</xpath>
<xpath expr="//span[text()='a']" position="replace">
<span class="o_key">e</span>
</xpath>
<xpath expr="//span[text()='j']" position="replace">
<span class="o_key">d</span>
</xpath>
<xpath expr="//span[text()='j']" position="replace">
<span class="o_key">d</span>
</xpath>
<xpath expr="//span[text()='p']" position="replace">
<span class="o_key">z</span>
</xpath>
<xpath expr="//span[text()='p']" position="replace">
<span class="o_key">z</span>
</xpath>
<xpath expr="//span[text()='n']" position="replace">
<span class="o_key">x</span>
</xpath>
<xpath expr="//span[text()='n']" position="replace">
<span class="o_key">x</span>
</xpath>
</t>
</templates> </templates>

View File

@ -1,53 +0,0 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
// Main navbar (with systray items: user menu, debug menu...)
@include media-breakpoint-down(sm) {
.o_main_navbar {
// Hide big things
.o_menu_brand,
.oe_topbar_name {
display: none;
}
// Collapse sections menu to hamburger
.o_menu_sections {
width: 46px;
}
.o_menu_sections_more {
.dropdown-toggle {
font-size: 17px;
}
.fa-plus:before {
content: "\f0c9";
}
}
// User menu paddings
.o_usr_menu {
margin: 0;
}
// Custom fullscreen layout when showing submenus
.dropdown-menu {
@include full-screen-dropdown();
background-color: $dropdown-bg;
overflow: auto;
// Higher height for dropdown items, for those with sausage fingers
.dropdown-item {
padding: {
bottom: 0.5rem;
top: 1rem;
}
font-size: 16px;
a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 ITerra - Sergey Shebanin
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.NavBar" t-inherit-mode="extension" owl="1">
<xpath expr="//t[@t-call='web.NavBar.SectionsMenu']" position="attributes">
<attribute
name="t-if"
t-translation="off"
>currentAppSections.length</attribute>
</xpath>
</t>
</templates>

View File

@ -6,14 +6,12 @@ import SearchPanel from "@web/legacy/js/views/search_panel";
import {deviceContext} from "@web_responsive/components/ui_context.esm"; import {deviceContext} from "@web_responsive/components/ui_context.esm";
import {patch} from "web.utils"; import {patch} from "web.utils";
const {useContext} = owl.hooks;
// Patch search panel to add functionality for mobile view // Patch search panel to add functionality for mobile view
patch(SearchPanel.prototype, "web_responsive.SearchPanelMobile", { patch(SearchPanel.prototype, "web_responsive.SearchPanelMobile", {
setup() { setup() {
this._super(); this._super();
this.state.mobileSearch = false; this.state.mobileSearch = false;
this.ui = useContext(deviceContext); this.ui = deviceContext;
}, },
getActiveSummary() { getActiveSummary() {
const selection = []; const selection = [];

View File

@ -54,7 +54,7 @@
.o_dropdown { .o_dropdown {
width: 100%; width: 100%;
margin: 15px 5px 0px 5px; margin: 15px 5px 0px 5px;
border: solid 1px darken(gray("200"), 20%); border: solid 1px darken($gray-200, 20%);
} }
.o_dropdown_toggler_btn { .o_dropdown_toggler_btn {
width: 100%; width: 100%;
@ -78,7 +78,7 @@
transform: translate3d(0, 0, 0) !important; transform: translate3d(0, 0, 0) !important;
box-shadow: none; box-shadow: none;
border: none; border: none;
color: gray("600"); color: $gray-600;
.divider { .divider {
margin: 0px; margin: 0px;
} }

View File

@ -7,7 +7,7 @@
<div <div
t-if="ui.isSmall" t-if="ui.isSmall"
class="o_search_panel_summary" class="o_search_panel_summary"
t-on-click.stop="state.mobileSearch = true" t-on-click.stop="() => this.state.mobileSearch = true"
> >
<div class="d-flex flex-wrap align-items-center"> <div class="d-flex flex-wrap align-items-center">
<i class="fa fa-fw fa-filter mr-1" /> <i class="fa fa-fw fa-filter mr-1" />

View File

@ -1,5 +1,6 @@
/** @odoo-module **/ /** @odoo-module **/
/* Copyright 2021 ITerra - Sergey Shebanin /* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {registry} from "@web/core/registry"; import {registry} from "@web/core/registry";
@ -7,7 +8,7 @@ import {debounce} from "@web/core/utils/timing";
import config from "web.config"; import config from "web.config";
import core from "web.core"; import core from "web.core";
const {Context} = owl; import Context from "web.Context";
// Legacy variant // Legacy variant
// TODO: remove when legacy code will dropped from odoo // TODO: remove when legacy code will dropped from odoo
@ -16,7 +17,7 @@ export const deviceContext = new Context({
isSmall: config.device.isMobile, isSmall: config.device.isMobile,
size: config.device.size_class, size: config.device.size_class,
SIZES: config.device.SIZES, SIZES: config.device.SIZES,
}); }).eval();
// New wowl variant // New wowl variant
// TODO: use default odoo device context when it will be realized // TODO: use default odoo device context when it will be realized
@ -26,7 +27,7 @@ const uiContextService = {
window.addEventListener( window.addEventListener(
"resize", "resize",
debounce(() => { debounce(() => {
const state = deviceContext.state; const state = deviceContext;
if (state.size !== ui.size) { if (state.size !== ui.size) {
state.size = ui.size; state.size = ui.size;
} }

View File

@ -1,569 +0,0 @@
/* Copyright 2019 Odoo S.A.
* Copyright 2021 ITerra - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
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.
*/
const config = require("web.config");
const core = require("web.core");
const KanbanRenderer = require("web.KanbanRenderer");
const KanbanView = require("web.KanbanView");
const KanbanQuickCreate = require("web.kanban_column_quick_create");
const _t = core._t;
const qweb = core.qweb;
KanbanQuickCreate.include({
init() {
this._super.apply(this, arguments);
this.isMobile = config.device.isMobile;
},
/**
* KanbanRenderer will decide can we close quick create or not
* @private
* @override
*/
_cancel: function () {
if (config.device.isMobile) {
this.trigger_up("close_quick_create");
}
},
/**
* Clear input when showed
* @override
*/
toggleFold: function () {
this._super.apply(this, arguments);
if (config.device.isMobile && !this.folded) {
this.$input.val("");
}
},
});
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 (config.device.isMobile) {
if (
this._scrollPosition &&
this.state.groupedBy.length &&
this.widgets.length
) {
const $column = this.widgets[this.activeColumnIndex].$el;
$column.scrollLeft(this._scrollPosition.left);
$column.scrollTop(this._scrollPosition.top);
}
this._computeTabPosition();
}
this._super.apply(this, arguments);
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
this.widgets = [];
this.columnOptions.recordsDraggable =
!config.device.isMobile &&
this.columnOptions.originRecordsDraggable;
this._renderView();
});
},
/**
* 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) {
const $column = this.widgets[this.activeColumnIndex].$el;
this._scrollPosition = {
left: $column.scrollLeft(),
top: $column.scrollTop(),
};
} else {
this._scrollPosition = null;
}
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
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 (config.device.isMobile) {
if (
this._canCreateColumn() &&
this.quickCreate &&
!this.quickCreate.folded
) {
this._onMobileQuickCreateClicked();
}
return this.widgets[this.activeColumnIndex].addQuickCreate();
}
return this._super.apply(this, arguments);
},
/**
* Overrides to restore the left property and the scrollTop on the updated
* column, and to enable the swipe handlers
*
* @override
*/
updateColumn: function (localID) {
if (config.device.isMobile) {
const index = _.findIndex(this.widgets, {db_id: localID});
const $column = this.widgets[index].$el;
const 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())
);
}
return this._super.apply(this, arguments);
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* Avoid drag'n'drop of kanban records on mobile and let the way to swipe
* @private
*/
_setState: function () {
const res = this._super.apply(this, arguments);
this.columnOptions.originRecordsDraggable =
this.columnOptions.recordsDraggable;
this.columnOptions.recordsDraggable =
!config.device.isMobile && this.columnOptions.recordsDraggable;
return res;
},
/**
* 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) {
const column = this.widgets[this.activeColumnIndex];
if (!column) {
return;
}
const 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) {
const lastItemIndex = this.widgets.length - 1;
const moveToIndex = this.activeColumnIndex;
let scrollToLeft = 0;
for (let i = 0; i < moveToIndex; i++) {
const columnWidth = this._getTabWidth(this.widgets[i]);
// Apply
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
const 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) {
// Use to compute the sum of the width of all tab
const widthChilds = this.widgets.reduce((total, column) => {
return total + this._getTabWidth(column);
}, 0);
// Apply a space around between child if the parent length is higher then the sum of the child width
const $tabs = this.$(".o_kanban_mobile_tabs");
$tabs.toggleClass(
"justify-content-between",
$tabs.outerWidth() >= widthChilds
);
}
},
/**
* Enables swipe event on the current column
*
* @private
*/
_enableSwipe: function () {
const step = _t.database.parameters.direction === "rtl" ? -1 : 1;
this.$el.swipe({
excludedElements: ".o_kanban_mobile_tabs",
swipeLeft: () => {
if (!config.device.isMobile) {
return;
}
const moveToIndex = this.activeColumnIndex + step;
if (moveToIndex < this.widgets.length) {
this._moveToGroup(moveToIndex, this.ANIMATE);
}
},
swipeRight: () => {
if (!config.device.isMobile) {
return;
}
const moveToIndex = this.activeColumnIndex - step;
if (moveToIndex > -1) {
this._moveToGroup(moveToIndex, this.ANIMATE);
}
},
});
},
/**
* Retrieve the outerWidth of a given widget column
*
* @param {KanbanColumn} column
* @returns {integer} outerWidth of the found column
* @private
*/
_getTabWidth: function (column) {
const 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) {
if (this.widgets.length === 0) {
return Promise.resolve();
}
if (moveToIndex >= 0 && moveToIndex < this.widgets.length) {
this.activeColumnIndex = moveToIndex;
}
const column = this.widgets[this.activeColumnIndex];
this._enableSwipe();
if (!column.data.isOpen) {
this.trigger_up("column_toggle_fold", {
db_id: column.db_id,
onSuccess: () => this._layoutUpdate(animate),
});
} else {
this._layoutUpdate(animate);
}
return Promise.resolve();
},
/**
* @override
* @private
*/
_renderExampleBackground: function () {
// Override to avoid display of example background
if (!config.device.isMobile) {
this._super.apply(this, arguments);
}
},
/**
* @override
* @private
*/
_renderGrouped: function (fragment) {
if (config.device.isMobile) {
const newFragment = document.createDocumentFragment();
this._super.apply(this, [newFragment]);
this.defs.push(
Promise.all(this.defs).then(() => {
const data = [];
_.each(this.state.data, function (group) {
if (!group.value) {
group = _.extend({}, group, {value: _t("Undefined")});
data.unshift(group);
} else {
data.push(group);
}
});
const kanbanColumnContainer = document.createElement("div");
kanbanColumnContainer.classList.add("o_kanban_columns_content");
kanbanColumnContainer.appendChild(newFragment);
fragment.appendChild(kanbanColumnContainer);
$(
qweb.render("KanbanView.MobileTabs", {
data: data,
quickCreateEnabled: this._canCreateColumn(),
})
).prependTo(fragment);
})
);
} else {
this._super.apply(this, arguments);
}
},
/**
* @override
* @private
*/
_renderView: function () {
const def = this._super.apply(this, arguments);
if (!config.device.isMobile) {
return def;
}
return def.then(() => {
if (this.state.groupedBy.length) {
// Force first column for kanban view, because the groupedBy can be changed
return this._moveToGroup(0);
}
if (this._canCreateColumn()) {
this._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.quickCreate.toggleFold();
this.$(".o_kanban_group").toggle(this.quickCreate.folded);
},
/**
* @private
* @param {MouseEvent} event
*/
_onMobileTabClicked: function (event) {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
this._moveToGroup($(event.currentTarget).index(), true);
},
/**
* @private
* @override
*/
_onCloseQuickCreate: function () {
if (this.widgets.length && this.quickCreate && !this.quickCreate.folded) {
this.$(".o_kanban_group").toggle(true);
this.quickCreate.toggleFold();
}
},
});
});

View File

@ -1,19 +1,9 @@
/* Copyright 2018 Tecnativa - Jairo Llopis /* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin * Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive", function (require) { odoo.define("web_responsive", function () {
"use strict"; "use strict";
const config = require("web.config");
const core = require("web.core");
const FormRenderer = require("web.FormRenderer");
const RelationalFields = require("web.relational_fields");
const ViewDialogs = require("web.view_dialogs");
const ListRenderer = require("web.ListRenderer");
const CalendarRenderer = require("web.CalendarRenderer");
const _t = core._t;
// Fix for iOS Safari to set correct viewport height // Fix for iOS Safari to set correct viewport height
// https://github.com/Faisal-Manzer/postcss-viewport-height-correction // https://github.com/Faisal-Manzer/postcss-viewport-height-correction
function setViewportProperty(doc) { function setViewportProperty(doc) {
@ -29,202 +19,4 @@ odoo.define("web_responsive", function (require) {
"resize", "resize",
_.debounce(setViewportProperty(document.documentElement), 100) _.debounce(setViewportProperty(document.documentElement), 100)
); );
RelationalFields.FieldStatus.include({
/**
* Fold all on mobiles.
*
* @override
*/
_setState: function () {
this._super.apply(this, arguments);
if (config.device.isMobile) {
_.map(this.status_information, (value) => {
value.fold = true;
});
}
},
});
// Sticky Column Selector
ListRenderer.include({
_renderView: function () {
return this._super.apply(this, arguments).then(() => {
const $col_selector = this.$el.find(
".o_optional_columns_dropdown_toggle"
);
if ($col_selector.length !== 0) {
const $th = this.$el.find("thead>tr:first>th:last");
$col_selector.appendTo($th);
}
});
},
_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!
// This prevents the action of sorting the column from being
// launched.
ev.stopPropagation();
this._super.apply(this, arguments);
},
});
// Responsive view "action" buttons
FormRenderer.include({
/**
* @override
*/
on_attach_callback: function () {
this._super.apply(this, arguments);
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
this._applyFormSizeClass();
this._render();
});
},
/**
* @override
*/
on_detach_callback: function () {
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
this._super.apply(this, arguments);
},
/**
* In mobiles, put all statusbar buttons in a dropdown.
*
* @override
*/
_renderHeaderButtons: function () {
const $buttons = this._super.apply(this, arguments);
if (
!config.device.isMobile ||
$buttons.children("button:not(.o_invisible_modifier)").length <= 2
) {
return $buttons;
}
// $buttons must be appended by JS because all events are bound
const $dropdown = $(
core.qweb.render("web_responsive.MenuStatusbarButtons")
);
$buttons.addClass("dropdown-menu").appendTo($dropdown);
return $dropdown;
},
});
/**
* Directly open popup dialog in mobile for search.
*/
RelationalFields.FieldMany2One.include({
start: function () {
var superRes = this._super.apply(this, arguments);
if (config.device.isMobile) {
this.$input.prop("readonly", true);
}
return superRes;
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* @private
* @override
*/
_bindAutoComplete: function () {
if (!config.device.isMobile) {
return this._super.apply(this, arguments);
}
},
/**
* @private
* @override
*/
_getSearchCreatePopupOptions: function () {
const options = this._super.apply(this, arguments);
_.extend(options, {
on_clear: () => this.reinitialize(false),
});
return options;
},
/**
* @private
* @override
*/
_toggleAutoComplete: function () {
if (config.device.isMobile) {
this._searchCreatePopup("search");
} else {
return this._super.apply(this, arguments);
}
},
});
/**
* Support for Clear button in search popup.
*/
ViewDialogs.SelectCreateDialog.include({
init: function () {
this._super.apply(this, arguments);
if (config.device.isMobile) {
this.viewType = "kanban";
}
this.on_clear =
this.options.on_clear ||
function () {
return undefined;
};
},
/**
* @override
*/
_prepareButtons: function () {
this._super.apply(this, arguments);
if (config.device.isMobile && this.options.disable_multiple_selection) {
this.__buttons.push({
text: _t("Clear"),
classes: "btn-secondary o_clear_button",
close: true,
click: function () {
this.on_clear();
},
});
}
},
});
CalendarRenderer.include({
/**
* @override
*/
on_attach_callback: function () {
this._super.apply(this, arguments);
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
// Hack to force calendar to reload their options and rerender
this.calendar.setOption("locale", moment.locale());
});
},
/**
* @override
*/
on_detach_callback: function () {
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
this._super.apply(this, arguments);
},
/**
* @override
*/
_getFullCalendarOptions: function () {
const options = this._super.apply(this, arguments);
Object.defineProperty(options.views.dayGridMonth, "columnHeaderFormat", {
get() {
return config.device.isMobile ? "ddd" : "dddd";
},
});
return options;
},
});
}); });

View File

@ -1,52 +0,0 @@
/* Copyright 2021 Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive.website_apps_menu", function (require) {
"use strict";
/*
* We can't require anything from website here because `web_responsive` doesn't depend on `website`.
* In this case we will try to get WebsiteNavbar class through the PublicRoot registry.
* WebsiteNavbar can be unavailable if `website` is not installed. In this case this file do nothing.
*/
const publicRoot = require("root.widget");
const lazyloader = require("web.public.lazyloader");
const registry = publicRoot._getRegistry();
function patchNavbar() {
const navbar = registry.get("WebsiteNavbar", false);
if (!navbar) return false;
navbar.Widget.include({
/**
* We don't need to load app menus
* @override
*/
async _loadAppMenus() {
return Promise.resolve();
},
/**
* We add a spinner for the user to understand the loading
* @override
*/
_onOeApplicationsShow: function () {
const icon = $(
document.querySelector("#oe_main_menu_navbar a.full > i")
);
icon.removeClass("fa fa-th-large").append(
$("<span/>", {class: "fa fa-spin fa-spinner"})
);
window.location.href = "/web#home";
// Prevent dropdown to be showed
return false;
},
});
const menu = $("#oe_applications");
menu.addClass("o_responsive_loaded").after(
"<span class='o_menu_brand'>" + menu.find("a.full").text() + "</span>"
);
return true;
}
// Try to patch navbar. If it is not in registry - make another try after lazyload
if (!patchNavbar()) {
lazyloader.allScriptsLoaded.then(patchNavbar);
}
});

View File

@ -1,113 +0,0 @@
/* Copyright 2019 Odoo S.A.
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
@include media-breakpoint-down(sm) {
.o_kanban_view {
width: 100%;
}
.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;
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.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;
background-color: gray("600");
font-weight: bold;
}
.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;
}
}
}
.modal {
z-index: 1052;
}
.o_kanban_view .o_column_quick_create {
.o_quick_create_folded {
display: none !important;
}
.o_quick_create_unfolded {
width: 100%;
}
}
}

View File

@ -1,8 +1,9 @@
/* Copyright 2018 Tecnativa - Jairo Llopis /* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin * Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
$chatter_zone_width: 35%; $chatter_zone_width: 35% !important;
// Support for long titles // Support for long titles
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
@ -19,10 +20,9 @@ html .o_web_client .o_action_manager .o_action {
overflow: auto; overflow: auto;
.o_content { .o_content {
overflow: visible; overflow: visible !important;
} }
} }
max-width: 100%; max-width: 100%;
} }
@ -78,43 +78,16 @@ html .o_web_client .o_action_manager .o_action {
box-shadow: inset 0 -5px #7c7bad; box-shadow: inset 0 -5px #7c7bad;
} }
} }
} .settings > .app_settings_block .o_settings_container {
} padding-left: 0;
padding-right: 0;
// Size of labels
.o_web_client {
&.o_chatter_position_sided {
.o_action_manager {
.o_content,
.modal-content {
@include media-breakpoint-up(xl, $o-extra-grid-breakpoints) {
.o_inner_group {
.o_td_label {
min-width: 260px !important;
}
}
}
@include media-breakpoint-between(lg, xl, $o-extra-grid-breakpoints) {
.o_group_col_6 {
width: 100% !important;
}
}
}
} }
} }
&:not(.o_chatter_position_sided) { .o_kanban_view .o_control_panel .o_cp_bottom_right .o_cp_pager .btn-group {
@include media-breakpoint-up(lg, $o-extra-grid-breakpoints) { top: -1px;
.o_action_manager { }
.o_content, .o_kanban_renderer {
.modal-content { width: 100%;
.o_inner_group {
.o_td_label {
min-width: 260px !important;
}
}
}
}
}
} }
} }
@ -124,13 +97,13 @@ html .o_web_client .o_action_manager .o_action {
max-width: 100%; max-width: 100%;
// Form views // Form views
.o_form_view { .o_form_editable {
.o_form_sheet { .o_form_sheet {
max-width: calc(100% - 32px); max-width: calc(100% - 32px) !important;
overflow-x: auto; overflow-x: auto;
} }
.o_td_label .o_form_label:not(.o_status):not(.o_calendar_invitation) { .o_cell .o_form_label:not(.o_status):not(.o_calendar_invitation) {
min-height: 23px; min-height: 23px;
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
margin-bottom: 10px; margin-bottom: 10px;
@ -139,10 +112,14 @@ html .o_web_client .o_action_manager .o_action {
.o_horizontal_separator { .o_horizontal_separator {
font-size: 14px; font-size: 14px;
} }
// Some UX improvements for form in edit mode // Some UX improvements for form in edit mode
@include media-breakpoint-down(sm) { @include media-breakpoint-down(sm) {
.o_field_widget { .nav-item {
vertical-align: middle; border-bottom: 1px solid #dee2e6;
}
.nav-tabs {
border-bottom: none;
} }
&.o_form_editable .o_field_widget { &.o_form_editable .o_field_widget {
&:not(.o_stat_info):not(.o_readonly_modifier):not(.oe_form_field_html):not(.o_field_image) { &:not(.o_stat_info):not(.o_readonly_modifier):not(.oe_form_field_html):not(.o_field_image) {
@ -153,13 +130,13 @@ html .o_web_client .o_action_manager .o_action {
} }
&.o_field_float_percentage, &.o_field_float_percentage,
&.o_field_monetary, &.o_field_monetary,
&.o_field_many2manytags, &.o_field_many2many_selection,
.o_field_many2one_selection { .o_field_many2one_selection {
align-items: center; align-items: center;
} }
.o_field_many2one_selection .o_input_dropdown, .o_field_many2one_selection .o_input_dropdown,
&.o_datepicker, &.o_datepicker,
&.o_field_partner_autocomplete { &.o_partner_autocomplete_info {
input { input {
min-height: 35px; min-height: 35px;
} }
@ -173,53 +150,34 @@ html .o_web_client .o_action_manager .o_action {
right: 6px; right: 6px;
bottom: auto; bottom: auto;
} }
} .o_field_many2many_selection .o_dropdown_button {
} top: 0 !important;
}
.o_FormRenderer_chatterContainer {
padding-top: 0;
.o_Activity_info {
flex-wrap: wrap;
}
.o_ActivityBox_title {
margin-bottom: 0;
}
.o_MessageList_separatorDate {
padding-bottom: 0;
}
}
// 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 // Sticky statusbar
.o_form_statusbar { .o_form_statusbar {
position: sticky; position: sticky !important;
top: 0; top: 0;
z-index: 2; z-index: 2;
} }
// Support for long title (with ellipsis) // Support for long title (with ellipsis)
.oe_title { .oe_title {
span.o_field_widget:not(.oe_inline) { .o_field_widget:not(.oe_inline) {
max-width: 100%; display: block;
text-overflow: ellipsis; span {
white-space: nowrap; display: block;
overflow: hidden; max-width: 100%;
width: initial; text-overflow: ellipsis;
&:active { white-space: nowrap;
white-space: normal; overflow: hidden;
width: initial;
&:active {
white-space: normal;
}
} }
} }
} }
@ -231,16 +189,11 @@ html .o_web_client .o_action_manager .o_action {
.oe_button_box { .oe_button_box {
.o_dropdown_more { .o_dropdown_more {
button:last-child { button:last-child {
border-right: 1px solid gray("400"); border-right: 1px solid $gray-400 !important;
} }
} }
} }
// Avoid overflow on forms with title and/or button box
.oe_title {
max-width: 100%;
}
.oe_button_box + .oe_title, .oe_button_box + .oe_title,
.oe_button_box + .oe_avatar + .oe_title { .oe_button_box + .oe_avatar + .oe_title {
width: 100%; width: 100%;
@ -257,53 +210,11 @@ html .o_web_client .o_action_manager .o_action {
width: auto !important; width: auto !important;
} }
// Make all input groups vertical
.o_group_col_6,
.o_group_col_8 {
width: 100%;
}
// Statusbar buttons dropdown for mobiles
.o_statusbar_buttons_dropdown {
border: {
bottom: 0;
radius: 0;
top: 0;
}
height: 100%;
}
.o_statusbar_buttons.dropdown-menu {
.btn {
border-radius: 0;
border: 0;
width: 100%;
margin-bottom: 0.2rem;
white-space: nowrap;
@include media-breakpoint-down(xs) {
max-width: 80vw;
overflow: hidden;
text-overflow: ellipsis;
}
&:last-child {
margin-bottom: 0;
}
}
}
.o_statusbar_status {
// Arrow from rightmost button exceeds allowed width
.o_arrow_button:first-child::before {
content: none;
display: none;
}
}
// Full width in form sheets // Full width in form sheets
.o_form_sheet, .o_form_sheet,
.o_FormRenderer_chatterContainer { .o_FormRenderer_chatterContainer {
min-width: auto; min-width: auto;
max-width: 98%; max-width: 98% !important;
} }
// Settings pages // Settings pages
@ -320,7 +231,7 @@ html .o_web_client .o_action_manager .o_action {
.o_Chatter_composer { .o_Chatter_composer {
&.o-has-current-partner-avatar { &.o-has-current-partner-avatar {
grid-template-columns: 0px 1fr; grid-template-columns: 0px 1fr;
padding: 1rem 1rem 1.5rem 1rem; padding: 1rem 1rem 1.5rem 1rem !important;
} }
.o_Composer_sidebarMain { .o_Composer_sidebarMain {
@ -343,71 +254,26 @@ html .o_web_client .o_action_manager .o_action {
display: none; display: none;
} }
} }
// Sided chatter, if user wants
.o_chatter_position_sided & {
@include media-breakpoint-up(lg) {
.o_form_view:not(.o_form_nosheet) {
display: flex;
flex-flow: row nowrap;
height: 100%;
.o_form_sheet_bg {
flex: 1 1 auto;
overflow: auto;
> .o_form_sheet {
min-width: unset;
}
}
.o_FormRenderer_chatterContainer {
border-left: 1px solid gray("400");
flex: 0 0 $chatter_zone_width;
max-width: initial;
min-width: initial;
overflow: auto;
.o_chatter_header_container {
padding-top: $grid-gutter-width * 0.5;
top: 0;
position: sticky;
background-color: $o-view-background-color;
z-index: 1;
// Scrollable input text to avoid hide conversation & buttons
.o_composer_text_field {
max-height: 120px;
overflow-y: auto !important; /* Forced because Odoo uses inline style */
}
.o_attachments_list {
overflow: auto;
max-height: $o-mail-attachment-image-size * 3;
margin-top: 0.4em;
}
.o_attachments_previews {
overflow: auto;
max-height: $o-mail-attachment-image-size * 6;
}
}
}
}
}
}
} }
// Sticky Header & Footer in List View // Sticky Header & Footer in List View
.o_list_view { .o_list_view {
.table-responsive { .table-responsive {
overflow: visible;
.o_list_table { .o_list_table {
// th & td are here for compatibility with chrome // th & td are here for compatibility with chrome
thead tr:nth-child(1) th { thead tr:nth-child(1) th {
position: sticky; position: sticky !important;
top: 0; top: 0;
z-index: 1; z-index: 999;
} }
thead tr:nth-child(1) th { thead tr:nth-child(1) th {
background-color: $o-list-footer-bg-color; background-color: var(--ListRenderer-thead-bg-color);
border-top: none !important;
border-bottom: none !important;
border-left: transparent;
box-shadow: inset 0 0 0 $o-community-color,
inset 0 -1px 0 $o-community-color;
} }
tfoot, tfoot,
tfoot tr:nth-child(1) td { tfoot tr:nth-child(1) td {
@ -416,6 +282,15 @@ html .o_web_client .o_action_manager .o_action {
} }
tfoot tr:nth-child(1) td { tfoot tr:nth-child(1) td {
background-color: $o-list-footer-bg-color; background-color: $o-list-footer-bg-color;
border-top: none !important;
border-bottom: none !important;
box-shadow: inset 0 1px 0 $o-community-color,
inset 0 0 0 $o-community-color;
}
}
.table {
thead tr:nth-child(1) th {
z-index: 1;
} }
} }
} }
@ -423,14 +298,39 @@ html .o_web_client .o_action_manager .o_action {
// Big checkboxes // Big checkboxes
.o_list_view, .o_list_view,
.o_settings_container .o_setting_box { .o_setting_container .o_setting_box {
.o_setting_right_pane { .o_setting_right_pane {
margin-left: 34px; margin-left: 34px;
} }
.custom-checkbox:not(.o_boolean_toggle) { .o-checkbox:not(.o_boolean_toggle) {
margin-right: 10px; margin-right: 10px;
margin-top: -6px;
&.d-inline-block {
display: block !important;
}
.form-check-input {
height: 24px;
width: 24px;
}
}
.o_optional_columns_dropdown {
.o-checkbox {
margin-top: 0;
}
.form-check-input {
height: 1em !important;
width: 1em !important;
}
}
}
.custom-control-label { .o_setting_container .o_setting_box {
.o_setting_right_pane {
margin-left: 34px;
}
.o-checkbox:not(.o_boolean_toggle) {
margin-right: 10px;
.form-check-label {
&::after { &::after {
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -444,18 +344,41 @@ html .o_web_client .o_action_manager .o_action {
} }
} }
} }
.o_list_view {
.custom-checkbox:not(.o_boolean_toggle) { .o_chatter_header_container {
top: -6px; padding-top: $grid-gutter-width * 0.5;
top: 0;
position: sticky;
background-color: $o-view-background-color;
z-index: 1;
}
.o_FormRenderer_chatterContainer {
&.o-isInFormSheetBg:not(.o-aside) {
background-color: $white;
&:not(.o-aside) {
width: auto;
border-top: 1px solid $border-color;
}
}
&.o-aside {
flex: 0 0 $chatter_zone_width;
max-width: initial;
min-width: initial;
overflow: auto;
} }
} }
@include media-breakpoint-down(sm) {
.o_base_settings body:not(.o_statusbar_buttons) {
.o_setting_container .oe-toolbar {
.settings z-index: 0 !important;
> .app_settings_block
.o_settings_container {
padding-left: 0;
padding-right: 0;
} }
} }
.o_inner_group > .mb-sm-0 {
margin-bottom: 0 !important;
}
.w-26 {
width: 26%;
}

View File

@ -1,35 +0,0 @@
/* Copyright 2021 ITerra - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
// Website main navbar and his AppsMenu button
#oe_main_menu_navbar {
#oe_applications .dropdown-toggle::after {
display: none;
}
a.full {
> i {
padding: 0 10px 0 0;
font-size: 17px;
}
font-size: 20px;
line-height: 46px;
@include media-breakpoint-down(sm) {
width: 46px;
overflow: hidden;
}
}
#oe_applications.o_responsive_loaded {
a.full {
width: 46px;
overflow: hidden;
}
}
.o_menu_brand {
white-space: nowrap;
padding: 0 16px 0 4px;
text-transform: capitalize;
@include media-breakpoint-down(sm) {
display: none;
}
}
}

View File

@ -4,108 +4,165 @@
Copyright 2018 Alexandre Díaz Copyright 2018 Alexandre Díaz
Copyright 2018 Tecnativa - Jairo Llopis Copyright 2018 Tecnativa - Jairo Llopis
Copyright 2021 ITerra - Sergey Shebanin Copyright 2021 ITerra - Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
--> -->
<templates id="form_view" xml:space="preserve"> <templates id="form_view" xml:space="preserve">
<!-- Template for buttons that display only the icon in xs --> <!-- Template for buttons that display only the icon in xs -->
<t t-name="web_responsive.icon_button"> <t t-name="web_responsive.icon_button" owl="1">
<i t-attf-class="fa fa-#{icon}" t-att-title="label" /> <i t-attf-class="fa fa-#{icon}" t-att-title="label" />
<span class="d-none d-sm-inline" t-esc="label" /> <span class="d-none d-sm-inline" t-esc="label" />
</t> </t>
<t t-name="web_responsive.MenuStatusbarButtons"> <t
<div class="dropdown"> t-name="web.ResponsiveFormView.Buttons"
t-inherit="web.FormView.Buttons"
owl="1"
t-inherit-mode="extension"
>
<!-- Change "Discard" button hotkey to "D" -->
<xpath
expr="//button[contains(@class, 'o_form_button_cancel')]"
position="attributes"
>
<attribute name="data-hotkey">d</attribute>
</xpath>
<xpath
expr="//button[contains(@class, 'o_form_button_create')]"
position="replace"
>
<button <button
class="o_statusbar_buttons_dropdown btn btn-secondary dropdown-toggle"
type="button" type="button"
data-toggle="dropdown" class="btn btn-secondary o_form_button_create"
aria-haspopup="true" data-hotkey="c"
aria-expanded="false" t-on-click.stop="create"
> >
<t t-call="web_responsive.icon_button"> <t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'cogs'" /> <t t-set="icon" t-value="'plus'" />
<t t-set="label">Quick actions</t> <t t-set="label"> Create</t>
</t> </t>
</button> </button>
<!-- A div.o_statusbar_buttons.dropdown-menu </xpath>
is appended here from JS -->
</div>
</t> </t>
<t t-extend="FormView.buttons">
<!-- Change "Edit" button hotkey to "E" --> <t
<t t-jquery=".o_form_button_edit" t-operation="attributes"> t-name="web.ResponsiveFormView"
<attribute name="accesskey">e</attribute> t-inherit="web.FormView"
</t> owl="1"
t-inherit-mode="extension"
>
<xpath
expr="//button[contains(@class, 'o_form_button_create')]"
position="replace"
>
<button
type="button"
class="btn btn-outline-primary o_form_button_create"
data-hotkey="c"
t-on-click.stop="create"
t-att-disabled="state.isDisabled"
>
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'plus'" />
<t t-set="label"> Create</t>
</t>
</button>
</xpath>
</t>
<t
t-name="web.ResponsiveFormStatusIndicator"
t-inherit="web.FormStatusIndicator"
owl="1"
>
<!-- Change "Discard" button hotkey to "D" --> <!-- Change "Discard" button hotkey to "D" -->
<t t-jquery=".o_form_button_cancel" t-operation="attributes"> <xpath
<attribute name="accesskey">d</attribute> expr="//button[contains(@class, 'o_form_button_cancel')]"
</t> position="attributes"
<!-- Add responsive icons to buttons --> >
<t t-jquery=".o_form_button_edit" t-operation="inner"> <attribute name="data-hotkey">d</attribute>
<t t-call="web_responsive.icon_button"> </xpath>
<t t-set="icon" t-value="'pencil'" />
<t t-set="label">Edit</t>
</t>
</t>
<t t-jquery=".o_form_button_create" t-operation="inner">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'plus'" />
<t t-set="label">Create</t>
</t>
</t>
<t t-jquery=".o_form_button_save" t-operation="inner">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'check'" />
<t t-set="label">Save</t>
</t>
</t>
<t t-jquery=".o_form_button_cancel" t-operation="inner">
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'times'" />
<t t-set="label">Discard</t>
</t>
</t>
</t> </t>
<t t-extend="KanbanView.buttons"> <t
t-name="web.ResponsiveKanbanView.Buttons"
t-inherit="web.KanbanView.Buttons"
owl="1"
t-inherit-mode="extension"
>
<!-- Add responsive icons to buttons --> <!-- Add responsive icons to buttons -->
<t t-jquery="button" t-operation="inner"> <xpath
<t t-call="web_responsive.icon_button"> expr="//button[contains(@class, 'o-kanban-button-new')]"
<t t-set="icon" t-value="'plus'" /> position="replace"
<t t-set="label" t-value="create_text || _t('Create')" /> >
</t> <button
</t> type="button"
class="btn btn-primary o-kanban-button-new"
accesskey="c"
t-on-click="() => this.createRecord(null)"
data-bounce-button=""
>
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'plus'" />
<t t-set="label"> Create</t>
</t>
</button>
</xpath>
</t> </t>
<t t-extend="ListView.buttons"> <t
<!-- Change "Discard" button hotkey to "D" --> t-name="web.ResponsiveListView.Buttons"
<t t-jquery=".o_list_button_discard" t-operation="attributes"> t-inherit="web.ListView.Buttons"
<attribute name="accesskey">d</attribute> owl="1"
</t> t-inherit-mode="extension"
>
<!-- Add responsive icons to buttons --> <!-- Add responsive icons to buttons -->
<t t-jquery=".o_list_button_add" t-operation="inner"> <xpath
<t t-call="web_responsive.icon_button"> expr="//button[contains(@class, 'o_list_button_add')]"
<t t-set="icon" t-value="'plus'" /> position="replace"
<t t-set="label">Create</t> >
</t> <button
</t> type="button"
<t t-jquery=".o_list_button_save" t-operation="inner"> class="btn btn-primary o_list_button_add"
<t t-call="web_responsive.icon_button"> data-hotkey="c"
<t t-set="icon" t-value="'check'" /> t-on-click="onClickCreate"
<t t-set="label">Save</t> data-bounce-button=""
</t> >
</t> <t t-call="web_responsive.icon_button">
<t t-jquery=".o_list_button_discard" t-operation="inner"> <t t-set="icon" t-value="'plus'" />
<t t-call="web_responsive.icon_button"> <t t-set="label"> Create</t>
<t t-set="icon" t-value="'times'" /> </t>
<t t-set="label">Discard</t> </button>
</t> </xpath>
</t> <xpath
</t> expr="//button[contains(@class, 'o_list_button_save')]"
<t t-extend="CalendarView.navigation_buttons"> position="replace"
<!-- Add responsive icons to buttons --> >
<t t-jquery=".o_calendar_button_today" t-operation="inner"> <button
<t t-call="web_responsive.icon_button"> type="button"
<t t-set="icon" t-value="'calendar-check-o'" /> class="btn btn-primary o_list_button_save"
<t t-set="label">Today</t> data-hotkey="s"
</t> t-on-click.stop="onClickSave"
</t> >
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'check'" />
<t t-set="label"> Save</t>
</t>
</button>
</xpath>
<xpath
expr="//button[contains(@class, 'o_list_button_discard')]"
position="replace"
>
<button
type="button"
class="btn btn-secondary o_list_button_discard"
data-hotkey="d"
t-on-click="onClickDiscard"
t-on-mousedown="onMouseDownDiscard"
>
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'times'" />
<t t-set="label"> Discard</t>
</t>
</button>
</xpath>
</t> </t>
</templates> </templates>

View File

@ -0,0 +1,14 @@
/** @odoo-module */
/* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {patch} from "@web/core/utils/patch";
import {FormController} from "@web/views/form/form_controller";
// Patch FormController to always load attachment alongwith the chatter on the side bar
patch(FormController.prototype, "web_responsive.FormController", {
setup() {
this._super();
this.hasAttachmentViewerInArch = false;
},
});

View File

@ -1 +0,0 @@
from . import test_res_users

View File

@ -1,14 +0,0 @@
# Copyright 2018 Alexandre Díaz
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
from odoo.tests import common
class TestResUsers(common.TransactionCase):
def test_chatter_position_wr(self):
user_public = self.env.ref("base.public_user")
user_public = user_public.with_user(user_public)
self.assertEqual(user_public.chatter_position, "sided")
user_public.write({"chatter_position": "normal"})
self.assertEqual(user_public.chatter_position, "normal")

View File

@ -1,17 +0,0 @@
<?xml version="1.0" ?>
<!--
Copyright 2018
@author Alexanre Díaz <dev@redneboa.es>
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
-->
<odoo>
<record id="view_users_form_simple_modif" model="ir.ui.view">
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form_simple_modif" />
<field name="arch" type="xml">
<xpath expr="//field[@name='email']" position="after">
<field name="chatter_position" readonly="0" />
</xpath>
</field>
</record>
</odoo>

View File

@ -2,22 +2,10 @@
<!-- <!--
Copyright 2018 Alexandre Díaz Copyright 2018 Alexandre Díaz
Copyright 2021 ITerra - Sergey Shebanin Copyright 2021 ITerra - Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html). License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
--> -->
<odoo> <odoo>
<template
id="webclient_bootstrap"
inherit_id="web.webclient_bootstrap"
name="App Drawer - Web Client"
>
<xpath expr="//t[@t-set='body_classname']" position="attributes">
<attribute
name="t-value"
add="+ ' o_chatter_position_' + (request.env.user.chatter_position or 'normal')"
separator=" "
/>
</xpath>
</template>
<template <template
id="responsive_web_layout" id="responsive_web_layout"
inherit_id="web.layout" inherit_id="web.layout"