[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
This commit is contained in:
anjeel.haria
2023-03-01 15:35:45 +01:00
committed by Taras Shabaranskyi
parent fa5e79acc1
commit 475d01f68a
42 changed files with 930 additions and 1868 deletions

View File

@@ -1,6 +1,7 @@
/** @odoo-module **/
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
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 {WebClient} from "@web/webclient/webclient";
import {patch} from "web.utils";
import {escapeRegExp} from "@web/core/utils/strings";
const {Component} = owl;
const {useState, useRef} = owl.hooks;
const {Component, useState, onPatched, onWillPatch} = owl;
// Patch WebClient to show AppsMenu instead of default app
patch(WebClient.prototype, "web_responsive.DefaultAppsMenu", {
setup() {
this._super();
useBus(this.env.bus, "APPS_MENU:STATE_CHANGED", (payload) => {
this.el.classList.toggle("o_apps_menu_opened", payload);
useBus(this.env.bus, "APPS_MENU:STATE_CHANGED", ({detail: state}) => {
document.body.classList.toggle("o_apps_menu_opened", state);
});
},
});
@@ -32,16 +33,96 @@ export class AppsMenu extends Component {
setup() {
super.setup();
this.state = useState({open: false});
this.menuService = useService("menu");
useBus(this.env.bus, "ACTION_MANAGER:UI-UPDATED", () => {
this.setState(false);
this.setOpenState(false, false);
});
useBus(this.env.bus, "APPS_MENU:CLOSE", () => {
this.setState(false);
this._setupKeyNavigation();
}
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;
this.env.bus.trigger("APPS_MENU:STATE_CHANGED", state);
_onWindowKeydown(direction) {
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) {
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;
}
if (menu.childrenTree) {
@@ -108,8 +199,7 @@ export class AppsMenuSearchBar extends Component {
offset: 0,
hasResults: false,
});
useAutofocus({selector: "input"});
this.searchBarInput = useRef("SearchBarInput");
this.searchBarInput = useAutofocus({refName: "SearchBarInput"});
this._searchMenus = debounce(this._searchMenus, 100);
// Store menu data in a format searchable by fuzzy.js
this._searchableMenus = [];
@@ -122,26 +212,24 @@ export class AppsMenuSearchBar extends Component {
}
// Set up key navigation
this._setupKeyNavigation();
}
willPatch() {
// Allow looping on results
if (this.state.offset < 0) {
this.state.offset = this.state.results.length + this.state.offset;
} 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);
onWillPatch(() => {
// Allow looping on results
if (this.state.offset < 0) {
this.state.offset = this.state.results.length + this.state.offset;
} else if (this.state.offset >= this.state.results.length) {
this.state.offset -= this.state.results.length;
}
}
});
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
*/
_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", () => {
this.state.offset = 0;
});
useHotkey("End", () => {
this.state.offset = this.state.results.length - 1;
});
useHotkey("Enter", () => {
if (this.state.results.length) {
this.el.querySelector(".highlight").click();
}
});
}
_onKeyDown(ev) {
@@ -224,9 +276,60 @@ export class AppsMenuSearchBar extends Component {
} else {
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";
AppsMenuSearchBar.template = "web_responsive.AppsMenuSearchResults";
Object.assign(NavBar.components, {AppsMenu, AppsMenuSearchBar});

View File

@@ -12,13 +12,12 @@
width: 100vw;
z-index: 200;
left: 0 !important;
top: $o-navbar-height !important;
}
.o_apps_menu_opened .o_main_navbar {
.o_menu_brand,
.o_menu_sections {
display: none;
display: none !important;
}
}
@@ -32,7 +31,7 @@
.fade-leave-to {
opacity: 0;
}
.dropdown-menu {
.dropdown-menu-custom {
@include full-screen-dropdown();
cursor: pointer;
background: url("../../img/home-menu-bg-overlay.svg"),
@@ -62,7 +61,24 @@
}
.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 {
box-shadow: none;
margin-bottom: 5px;
@@ -70,31 +86,10 @@
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,
a:focus img {
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
@@ -129,21 +124,20 @@
.search-input {
display: flex;
justify-items: middle;
box-shadow: inset 0 1px 0 rgba(gray("white"), 0.1),
0 1px 0 rgba(gray("black"), 0.1);
text-shadow: 0 1px 0 rgba(gray("black"), 0.5);
justify-items: center;
box-shadow: inset 0 1px 0 rgba($white, 0.1), 0 1px 0 rgba($black, 0.1);
text-shadow: 0 1px 0 rgba($black, 0.5);
border-radius: 4px;
padding: 0.4rem 0.8rem;
margin-bottom: 1rem;
background-color: rgba(gray("white"), 0.1);
background-color: rgba($white, 0.1);
@include media-breakpoint-up(md) {
padding: 0.8rem 1.2rem;
}
.search-icon {
color: gray("white");
color: $white;
font-size: 1.5rem;
margin-right: 1rem;
padding-top: 1px;
@@ -153,19 +147,27 @@
height: 2rem;
background: none;
border: none;
color: gray("white");
color: $white;
display: block;
padding: 1px 2px 2px 2px;
box-shadow: none;
&::placeholder {
color: gray("white");
color: $white;
opacity: 0.5;
}
}
}
// Allow to scroll only on results, keeping static search box above
.search-results {
.text-ellipsis {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.text-primary {
color: red !important;
}
margin-top: 1rem;
max-height: calc(100vh - #{$o-navbar-height} - 8rem) !important;
overflow: auto;
@@ -177,7 +179,7 @@
background-position: left;
background-repeat: no-repeat;
background-size: contain;
color: gray("white");
color: $white;
cursor: pointer;
line-height: 2.5rem;
padding-left: 3.5rem;
@@ -185,7 +187,7 @@
font-weight: 100;
&.highlight,
&:hover {
background-color: rgba(gray("black"), 0.11);
background-color: rgba($black, 0.11);
}
b {
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" ?>
<!-- Copyright 2018 Tecnativa - Jairo Llopis
Copyright 2021 ITerra - Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<t t-inherit="web.NavBar.AppsMenu" t-inherit-mode="extension" owl="1">
@@ -8,23 +9,22 @@
<!-- Same hotkey as in EE -->
<AppsMenu>
<AppsMenuSearchBar />
<MenuItem
<DropdownItem
t-foreach="apps"
t-as="app"
t-key="app.id"
class="o_app"
t-att-class="{ o_dropdown_active: menuService.getCurrentApp() === app }"
payload="app"
class="'o_app'"
dataset="{ menuXmlid: app.xmlid, section: app.id }"
href="getMenuItemHref(app)"
onSelected="() => this.onNavBarDropdownItemSelection(app)"
>
<a t-att-href="getMenuItemHref(app)">
<img
class="o-app-icon"
draggable="false"
t-attf-src="data:image/png;base64,{{app.webIconData}}"
/>
class="o-app-icon"
draggable="false"
t-att-src="getWebIconData(app)"
/>
<div t-esc="app.name" />
</a>
</MenuItem>
</DropdownItem>
</AppsMenu>
</xpath>
</t>
@@ -35,16 +35,11 @@
class="dropdown-toggle"
title="Home Menu"
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" />
</button>
<div
t-if="state.open"
class="dropdown-menu"
style="top: 46px; left: 0px;"
t-transition="fade"
>
<div t-if="state.open" class="dropdown-menu-custom">
<t t-slot="default" />
</div>
</div>
@@ -70,17 +65,31 @@
/>
</div>
<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)" />
<a
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-att-data-menu-id="menu.id"
t-att-data-action-id="menu.actionID"
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>
</div>
</div>

View File

@@ -1,11 +1,12 @@
/** @odoo-module **/
/* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import {AttachmentViewer} from "@mail/components/attachment_viewer/attachment_viewer";
import {patch} from "web.utils";
const {useState} = owl.hooks;
import {registerPatch} from "@mail/model/model_core";
const {useState} = owl;
// Patch attachment viewer to add min/max buttons capability
patch(AttachmentViewer.prototype, "web_responsive.AttachmentViewer", {
@@ -15,8 +16,22 @@ patch(AttachmentViewer.prototype, "web_responsive.AttachmentViewer", {
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). */
// Attachment Viewer
.o_web_client.o_chatter_position_sided .o_DialogManager_dialog {
.o_web_client .o_DialogManager_dialog {
/* Show sided viewer on large screens */
@include media-breakpoint-up(lg) {
position: static;
@media (min-width: 1533px) {
&:not(:has(.o_AttachmentDeleteConfirm)) {
position: static;
}
.o_AttachmentViewer_main {
padding-bottom: 20px;
}
@@ -22,7 +24,7 @@
width: $chatter_zone_width;
&.o_AttachmentViewer_maximized {
width: 100%;
width: 100% !important;
}
/* 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_headerItemButtonMaximize {
display: none;
display: none !important;
}
}
}
/* 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_headerItemButtonMaximize {
display: none;
display: none !important;
}
}
.o_apps_menu_opened .o_AttachmentViewer {
display: none !important;
}

View File

@@ -16,8 +16,8 @@
>
<div
t-if="!state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMaximize"
t-on-click="state.maximized=true"
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 }"
role="button"
title="Maximize"
aria-label="Maximize"
@@ -26,8 +26,8 @@
</div>
<div
t-if="state.maximized"
class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonMinimize"
t-on-click="state.maximized=false"
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 }"
role="button"
title="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 **/
/* Copyright 2021 ITerra - Sergey Shebanin
* Copyright 2023 Onestein - Anjeel Haria
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
import LegacyControlPanel from "web.ControlPanel";
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 {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.
// We extend them two mostly the same way.
@@ -20,7 +21,7 @@ patch(LegacyControlPanel.prototype, "web_responsive.LegacyControlPanelMobile", {
this.state = useState({
mobileSearchMode: this.props.withBreadcrumbs ? "" : "quick",
});
this.ui = useContext(deviceContext);
this.ui = deviceContext;
},
setMobileSearchMode(ev) {
this.state.mobileSearchMode = ev.detail;
@@ -34,12 +35,11 @@ patch(ControlPanel.prototype, "web_responsive.ControlPanelMobile", {
this.state = useState({
mobileSearchMode: "",
});
this.ui = useContext(deviceContext);
this.ui = deviceContext;
},
setMobileSearchMode(ev) {
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
@media (min-width: 1900px) {
.o_action_manager & .o_cp_top_left,
.o_action_manager & .o_cp_bottom_left {
.o_cp_top_left,
.o_cp_bottom_left {
width: 60%;
}
.o_action_manager & .o_cp_top_right,
.o_action_manager & .o_cp_bottom_right {
.o_cp_top_right,
.o_cp_bottom_right {
width: 40%;
}
}
@@ -42,9 +42,6 @@
// It doesn't work on iOS Safari, but it looks similar as
// without this patch. With this patch it looks better for
// other browsers.
position: sticky;
left: 0;
z-index: 3;
// Arrange buttons to use space better
.o_cp_top_left,
@@ -53,24 +50,24 @@
}
.o_cp_top_left {
flex-basis: 80%;
max-width: 80%;
flex-basis: 89%;
max-width: 89%;
}
.o_cp_top_right {
flex-basis: 20%;
flex-basis: 11%;
}
.o_cp_bottom {
position: relative; // Necessary for dropdown menu positioning
display: block;
margin: 0;
min-height: 30px !important;
}
.o_cp_bottom_left {
float: left;
margin: 5px 0;
max-width: 80%;
}
.o_cp_bottom_right {
@@ -102,7 +99,6 @@
.dropdown-toggle {
margin: 0px 2px;
height: 100%;
padding-right: 0.5rem !important;
}
.dropdown {
height: 100%;
@@ -230,16 +226,18 @@
z-index: $zindex-modal;
overflow: auto;
.o_mobile_search_header {
height: 46px;
margin-bottom: 10px;
background-color: var(--mobileSearch__header-bg, #{$o-brand-odoo});
display: flex;
min-height: $o-navbar-height;
justify-content: space-between;
width: 100%;
background-color: $o-brand-odoo;
color: white;
span:active {
background-color: darken($o-brand-primary, 10%);
}
span {
cursor: pointer;
.o_mobile_search_button {
color: white;
&:active {
background-color: darken($o-brand-primary, 10%);
}
}
}
.o_searchview_input_container {
@@ -266,10 +264,10 @@
line-height: 2rem;
width: 100%;
margin: 15px 5px 0px 5px;
border: solid 1px darken(gray("200"), 20%);
border: solid 1px darken($gray-200, 20%);
}
.dropdown.show > .dropdown-toggle {
background-color: gray("200");
background-color: $gray-200;
}
.dropdown-toggle {
width: 100%;
@@ -294,7 +292,7 @@
max-height: 100%;
box-shadow: none;
border: none;
color: gray("600");
color: $gray-600;
.divider {
margin: 0px;
}

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Copyright 2021 Sergey Shebanin
Copyright 2023 Onestein - Anjeel Haria
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
<templates>
<!-- Legacy control panel templates -->
@@ -43,7 +44,7 @@
t-att-class="state.mobileSearchMode == 'quick' ? 'o_searchview_quick' : 'o_searchview_mobile'"
role="search"
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">
<i
@@ -59,12 +60,12 @@
<button
t-if="props.withBreadcrumbs"
class="btn btn-link fa fa-arrow-left"
t-on-click.stop="state.mobileSearchMode = ''"
t-on-click.stop="() => { state.mobileSearchMode = '' }"
/>
<SearchBar fields="fields" />
<button
class="btn fa fa-filter"
t-on-click.stop="state.mobileSearchMode = 'full'"
t-on-click.stop="() => { state.mobileSearchMode = 'full' }"
/>
</t>
<t
@@ -74,7 +75,7 @@
<t t-if="state.mobileSearchMode == ''">
<button
class="btn btn-link fa fa-search"
t-on-click.stop="state.mobileSearchMode = 'quick'"
t-on-click.stop="() => { state.mobileSearchMode = 'quick' }"
/>
</t>
</t>
@@ -97,19 +98,21 @@
<t t-name="web_responsive.LegacyMobileSearchView" owl="1">
<div class="o_cp_mobile_search">
<div class="o_mobile_search_header">
<span
class="o_mobile_search_close float-left mt16 mb16 mr8 ml16"
t-on-click.stop="state.mobileSearchMode = 'quick'"
<button
type="button"
class="o_mobile_search_button btn"
t-on-click="() => state.mobileSearchMode = false"
>
<i class="fa fa-arrow-left" />
<strong class="float-right ml8">FILTER</strong>
</span>
<span
class="float-right o_mobile_search_clear_facets mt16 mr16"
t-on-click.stop="model.dispatch('clearQuery')"
<strong class="ms-2">FILTER</strong>
</button>
<button
type="button"
class="o_mobile_search_button btn"
t-on-click="() => this.model.dispatch('clearQuery')"
>
<t>CLEAR</t>
</span>
CLEAR
</button>
</div>
<SearchBar fields="fields" />
<div class="o_mobile_search_filter o_search_options mb8 mt8 ml16 mr16">
@@ -134,91 +137,13 @@
</div>
<div
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>
</div>
</div>
</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">
<div>
<t t-if="!env.isSmall" t-call="web.SearchBar" />
@@ -228,7 +153,7 @@
<button
t-if="props.withBreadcrumbs"
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">
<t t-call="web.SearchBar.Facets" />
@@ -239,7 +164,7 @@
</div>
<button
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>
</t>
@@ -252,7 +177,7 @@
class="o_searchview o_searchview_mobile"
role="search"
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" />
</div>
@@ -266,14 +191,14 @@
<div class="o_mobile_search_header">
<span
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" />
<strong class="float-right ml8">FILTER</strong>
</span>
<span
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>
</span>
@@ -292,7 +217,7 @@
</div>
<div
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>
</div>

View File

@@ -36,36 +36,4 @@
>props.withAccessKey ? 'x' : false</attribute>
</xpath>
</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>

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 {patch} from "web.utils";
const {useContext} = owl.hooks;
// Patch search panel to add functionality for mobile view
patch(SearchPanel.prototype, "web_responsive.SearchPanelMobile", {
setup() {
this._super();
this.state.mobileSearch = false;
this.ui = useContext(deviceContext);
this.ui = deviceContext;
},
getActiveSummary() {
const selection = [];

View File

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

View File

@@ -7,7 +7,7 @@
<div
t-if="ui.isSmall"
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">
<i class="fa fa-fw fa-filter mr-1" />

View File

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