Tower: upload web_responsive 19.0.1.0.2 (was 19.0.1.0.2, via marketplace)
All checks were successful
addon-qualify / qualify (push) Successful in 13s
All checks were successful
addon-qualify / qualify (push) Successful in 13s
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
/* global document, location, window */
|
||||
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Component, onWillStart, useState} from "@odoo/owl";
|
||||
import {useBus, useService} from "@web/core/utils/hooks";
|
||||
import {AppMenuItem} from "@web_responsive/components/apps_menu_item/apps_menu_item.esm";
|
||||
import {AppsMenuSearchBar} from "@web_responsive/components/menu_searchbar/searchbar.esm";
|
||||
import {NavBar} from "@web/webclient/navbar/navbar";
|
||||
import {WebClient} from "@web/webclient/webclient";
|
||||
import {browser} from "@web/core/browser/browser";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {router} from "@web/core/browser/router";
|
||||
import {session} from "@web/session";
|
||||
import {useHotkey} from "@web/core/hotkeys/hotkey_hook";
|
||||
import {user} from "@web/core/user";
|
||||
import {BurgerMenu} from "@web/webclient/burger_menu/burger_menu";
|
||||
|
||||
// Patch WebClient to show AppsMenu instead of default app
|
||||
patch(WebClient.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
useBus(this.env.bus, "APPS_MENU:STATE_CHANGED", ({detail: state}) => {
|
||||
document.body.classList.toggle("o_apps_menu_opened", state);
|
||||
});
|
||||
this.user = user;
|
||||
onWillStart(async () => {
|
||||
const is_redirect_home = await this.orm.searchRead(
|
||||
"res.users",
|
||||
[["id", "=", this.user.userId]],
|
||||
["is_redirect_home"]
|
||||
);
|
||||
user.updateContext({
|
||||
is_redirect_to_home: is_redirect_home[0]?.is_redirect_home,
|
||||
});
|
||||
});
|
||||
this.redirect = false;
|
||||
},
|
||||
_loadDefaultApp() {
|
||||
if (user.context.is_redirect_to_home) {
|
||||
this.env.bus.trigger("APPS_MENU:STATE_CHANGED", true);
|
||||
} else {
|
||||
super._loadDefaultApp();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export class AppsMenu extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({open: false});
|
||||
this.theme = session.apps_menu.theme || "milk";
|
||||
this.menuService = useService("menu");
|
||||
browser.localStorage.setItem("redirect_menuId", "");
|
||||
if (user.context.is_redirect_to_home) {
|
||||
this.router = router;
|
||||
const menuId = Number(this.router.current.menu_id || 0);
|
||||
this.state = useState({open: menuId === 0});
|
||||
}
|
||||
useBus(this.env.bus, "ACTION_MANAGER:UI-UPDATED", () => {
|
||||
this.setOpenState(false);
|
||||
});
|
||||
this._setupKeyNavigation();
|
||||
}
|
||||
|
||||
setOpenState(open_state) {
|
||||
this.state.open = open_state;
|
||||
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");
|
||||
});
|
||||
}
|
||||
|
||||
_onWindowKeydown(direction) {
|
||||
const focusableInputElements = document.querySelectorAll(".o-app-menu-item");
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
onMenuClick() {
|
||||
if (!user.context.is_redirect_to_home) {
|
||||
this.setOpenState(!this.state.open);
|
||||
} else {
|
||||
const redirect_menuId =
|
||||
browser.localStorage.getItem("redirect_menuId") || "";
|
||||
if (!redirect_menuId) {
|
||||
this.setOpenState(true);
|
||||
} else {
|
||||
this.setOpenState(!this.state.open);
|
||||
}
|
||||
const {href, hash} = location;
|
||||
const menuId = this.router.current.menu_id;
|
||||
if (menuId && menuId !== redirect_menuId) {
|
||||
browser.localStorage.setItem(
|
||||
"redirect_menuId",
|
||||
this.router.current.menu_id
|
||||
);
|
||||
}
|
||||
|
||||
if (href.includes(hash)) {
|
||||
window.history.replaceState(null, "", href.replace(hash, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add this patch after the WebClient patch
|
||||
patch(NavBar.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
|
||||
useBus(this.env.bus, "APP_MENU:TOGGLE_SIDEBAR", () => {
|
||||
this._openAppMenuSidebar();
|
||||
});
|
||||
},
|
||||
|
||||
openAppMenu() {
|
||||
this.env.bus.trigger("APP_MENU:OPEN_APP_MENU");
|
||||
this._closeAppMenuSidebar();
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(AppsMenu, {
|
||||
template: "web_responsive.AppsMenu",
|
||||
props: {
|
||||
slots: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(NavBar.components, {AppsMenu, AppMenuItem, AppsMenuSearchBar});
|
||||
|
||||
// Add this patch after the WebClient patch
|
||||
patch(BurgerMenu.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
},
|
||||
|
||||
_openAppMenuSidebarMobile() {
|
||||
this.env.bus.trigger("APP_MENU:TOGGLE_SIDEBAR");
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,117 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
:root {
|
||||
.o_grid_apps_menu[data-theme="milk"] {
|
||||
--app-menu-background:
|
||||
url("../../img/home-menu-bg-overlay.svg"),
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
#{$app-menu-background-color},
|
||||
#{desaturate(lighten($app-menu-background-color, 20%), 15)}
|
||||
);
|
||||
}
|
||||
|
||||
.o_grid_apps_menu[data-theme="community"] {
|
||||
--app-menu-background:
|
||||
url("../../img/home-menu-bg-overlay.svg"),
|
||||
linear-gradient(
|
||||
to bottom,
|
||||
#{$o-brand-primary},
|
||||
#{desaturate(lighten($o-brand-primary, 20%), 15)}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin full-screen-dropdown {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
height: 100%;
|
||||
max-height: calc(var(--vh100, 100vh) - #{$o-navbar-height});
|
||||
max-height: calc(100dvh - #{$o-navbar-height});
|
||||
position: fixed;
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
z-index: 1000;
|
||||
left: 0 !important;
|
||||
}
|
||||
|
||||
.o_apps_menu_opened .o_main_navbar {
|
||||
.o_menu_brand,
|
||||
.o_menu_sections {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// hide and save odoo default QUnit tests
|
||||
.o_navbar_apps_menu.hide .dropdown-toggle {
|
||||
position: absolute !important;
|
||||
z-index: -100 !important;
|
||||
}
|
||||
|
||||
// Iconized full screen apps menu
|
||||
.o_grid_apps_menu {
|
||||
&__button {
|
||||
background: unset;
|
||||
border: unset;
|
||||
outline: unset;
|
||||
margin-right: 0.25rem;
|
||||
min-height: $o-navbar-height;
|
||||
height: $o-navbar-height;
|
||||
width: $o-navbar-height;
|
||||
color: $o-navbar-brand-color;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: $o-navbar-entry-bg--hover;
|
||||
}
|
||||
}
|
||||
|
||||
.o-app-menu-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||||
width: 100%;
|
||||
gap: 0.25rem;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-menu-container {
|
||||
@include full-screen-dropdown();
|
||||
overflow: auto;
|
||||
background-clip: border-box;
|
||||
padding: 1rem 0.5rem;
|
||||
gap: 1rem;
|
||||
background: var(--app-menu-background);
|
||||
background-size: cover;
|
||||
border-radius: 0;
|
||||
// Display apps in a grid
|
||||
align-content: flex-start;
|
||||
display: flex !important;
|
||||
z-index: 1024 !important;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
|
||||
// Hide app icons when searching
|
||||
.has-results ~ .o-app-menu-list {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding: {
|
||||
left: calc((100vw - 850px) / 2);
|
||||
right: calc((100vw - 850px) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar positioning
|
||||
.o_app_menu_sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Onestein - Anjeel Haria
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<t t-inherit="web.NavBar.AppsMenu" t-inherit-mode="extension">
|
||||
<!-- odoo 18 has created a left sidebar where the button of all apps
|
||||
and the dropdown for the user where can logout
|
||||
we had to disable the lef sidebar to keep our web_resposive toggle button working
|
||||
and to keep the user dropdown in its original place -->
|
||||
<xpath expr="//t[@t-if='this.ui.isSmall']" position="attributes">
|
||||
<attribute name="t-if">false</attribute>
|
||||
</xpath>
|
||||
<!-- The kanban dropdown is replaced with the odoo default one
|
||||
as the default one took physical place in the DOM -->
|
||||
<xpath expr="//Dropdown" position="replace">
|
||||
<t t-if="this.ui.isSmall">
|
||||
<t t-call="web.NavBar.AppsMenu.Sidebar" />
|
||||
</t>
|
||||
<t t-else="" />
|
||||
<AppsMenu>
|
||||
<t t-set-slot="search_bar">
|
||||
<AppsMenuSearchBar />
|
||||
</t>
|
||||
<AppMenuItem
|
||||
t-foreach="apps"
|
||||
t-as="app"
|
||||
t-key="app.id"
|
||||
app="app"
|
||||
currentApp="currentApp"
|
||||
href="getMenuItemHref(app)"
|
||||
onClick="onNavBarDropdownItemSelection.bind(this)"
|
||||
/>
|
||||
</AppsMenu>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<!-- Apps menu -->
|
||||
<t t-name="web_responsive.AppsMenu">
|
||||
<div class="o_grid_apps_menu" t-att-data-theme="theme">
|
||||
<button
|
||||
class="o_grid_apps_menu__button"
|
||||
title="Home Menu"
|
||||
data-hotkey="h"
|
||||
t-on-click.stop="onMenuClick"
|
||||
>
|
||||
<i class="oi oi-apps fs-4" />
|
||||
</button>
|
||||
<div t-if="state.open" class="app-menu-container">
|
||||
<t t-slot="search_bar" />
|
||||
<div class="o-app-menu-list">
|
||||
<t t-slot="default" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- Apps Menu Sidebar -->
|
||||
<t t-inherit="web.NavBar.AppsMenu.Sidebar" t-inherit-mode="extension">
|
||||
<xpath expr="//i[hasclass('fa fa-bars')]" position="replace">
|
||||
<!-- Remove the burger menu icon -->
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div[hasclass('o_app_menu_sidebar')]" position="attributes">
|
||||
<attribute
|
||||
name="class"
|
||||
>o_app_menu_sidebar position-fixed top-0 bottom-0 start-100 d-flex flex-column flex-nowrap</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<!-- Section Menu Items -->
|
||||
<t t-inherit="web.SectionMenu" t-inherit-mode="extension">
|
||||
<!-- Add cursor pointer to menu items -->
|
||||
<xpath expr="//li[@t-on-click]" position="attributes">
|
||||
<attribute name="class">cursor-pointer</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<!-- Burger Menu -->
|
||||
<t t-inherit="web.BurgerMenu" t-inherit-mode="extension">
|
||||
<xpath expr="//button[hasclass('o_mobile_menu_toggle')]" position="after">
|
||||
<button
|
||||
class="o_mobile_menu_toggle o_nav_entry o-no-caret d-md-none border-0 pe-3"
|
||||
t-on-click.prevent="_openAppMenuSidebarMobile"
|
||||
>
|
||||
<i class="oi oi-panel-right" />
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Component, xml} from "@odoo/owl";
|
||||
import {registry} from "@web/core/registry";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {user} from "@web/core/user";
|
||||
|
||||
class AppsMenuPreferences extends Component {
|
||||
setup() {
|
||||
this.action = useService("action");
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
async _onClick() {
|
||||
const onClose = () => this.action.doAction("reload_context");
|
||||
const action = await this.action.loadAction(
|
||||
"web_responsive.res_users_view_form_apps_menu_preferences_action"
|
||||
);
|
||||
this.action.doAction({...action, res_id: this.user.userId}, {onClose}).then();
|
||||
}
|
||||
}
|
||||
|
||||
AppsMenuPreferences.template = xml`
|
||||
<div class="o-dropdown dropdown o-dropdown--no-caret">
|
||||
<button
|
||||
role="button"
|
||||
type="button"
|
||||
title="App Menu Preferences"
|
||||
class="dropdown-toggle o-dropdown--narrow"
|
||||
t-on-click="_onClick">
|
||||
<i class="fa fa-tint fa-lg px-1"/>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
registry
|
||||
.category("systray")
|
||||
.add("AppMenuTheme", {Component: AppsMenuPreferences}, {sequence: 100});
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Component, onWillUpdateProps} from "@odoo/owl";
|
||||
import {getWebIconData} from "@web_responsive/components/apps_menu_tools.esm";
|
||||
|
||||
export class AppMenuItem extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.webIconData = getWebIconData(this.props.app);
|
||||
onWillUpdateProps(this.onUpdateProps);
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
const {currentApp} = this.props;
|
||||
return currentApp && currentApp.id === this.props.app.id;
|
||||
}
|
||||
|
||||
get className() {
|
||||
const classItems = ["o-app-menu-item"];
|
||||
if (this.isActive) {
|
||||
classItems.push("active");
|
||||
}
|
||||
return classItems.join(" ");
|
||||
}
|
||||
|
||||
onUpdateProps(nextProps) {
|
||||
this.webIconData = getWebIconData(nextProps.app);
|
||||
}
|
||||
|
||||
onClick() {
|
||||
if (typeof this.props.onClick === "function") {
|
||||
this.props.onClick(this.props.app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(AppMenuItem, {
|
||||
template: "web_responsive.AppMenuItem",
|
||||
props: {
|
||||
app: Object,
|
||||
href: String,
|
||||
currentApp: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
},
|
||||
onClick: Function,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
:root {
|
||||
.o_grid_apps_menu[data-theme="milk"] {
|
||||
--app-menu-text-color: #{$app-menu-text-color};
|
||||
--app-menu-text-shadow: 1px 1px 1px #{rgba($white, 0.4)};
|
||||
--app-menu-hover-background: #{rgba(white, 0.4)};
|
||||
}
|
||||
|
||||
.o_grid_apps_menu[data-theme="community"] {
|
||||
--app-menu-text-color: white;
|
||||
--app-menu-text-shadow: 1px 1px 1px #{rgba(black, 0.4)};
|
||||
--app-menu-hover-background: #{rgba(white, 0.2)};
|
||||
}
|
||||
}
|
||||
|
||||
.o-app-menu-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 4px;
|
||||
gap: 0.25rem;
|
||||
transition:
|
||||
ease box-shadow,
|
||||
transform,
|
||||
0.3s;
|
||||
background: unset;
|
||||
outline: unset;
|
||||
border: unset;
|
||||
padding: 0.75rem 0.5rem;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
white-space: normal;
|
||||
user-select: none;
|
||||
height: -moz-available;
|
||||
height: max-content;
|
||||
|
||||
&__name {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-size: 1em;
|
||||
text-shadow: var(--app-menu-text-shadow);
|
||||
color: var(--app-menu-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
height: auto;
|
||||
max-width: 64px;
|
||||
width: 64px;
|
||||
aspect-ratio: 1;
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
box-shadow: $app-menu-box-shadow;
|
||||
}
|
||||
|
||||
&__active {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
right: 2px;
|
||||
text-shadow: 0 0 2px rgba(250, 250, 250, 0.6);
|
||||
color: $app-menu-text-color;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 6px 12px -8px transparentize($app-menu-text-color, 0.6);
|
||||
background-color: var(--app-menu-hover-background) !important;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Onestein - Anjeel Haria
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<t t-name="web_responsive.AppMenuItem">
|
||||
<a
|
||||
t-att-class="className"
|
||||
role="button"
|
||||
t-att-data-menu-xmlid="props.app.xmlid"
|
||||
t-att-href="props.href"
|
||||
t-on-click="onClick"
|
||||
draggable="false"
|
||||
>
|
||||
<div
|
||||
class="position-relative o_app"
|
||||
t-att-data-menu-xmlid="props.app.xmlid"
|
||||
>
|
||||
<img
|
||||
class="o-app-menu-item__icon rounded-3"
|
||||
draggable="false"
|
||||
t-att-src="webIconData"
|
||||
/>
|
||||
<i t-if="isActive" class="fa fa-check-circle o-app-menu-item__active" />
|
||||
</div>
|
||||
<span class="o-app-menu-item__name" t-att-title="props.app.name">
|
||||
<t t-out="props.app.name" />
|
||||
</span>
|
||||
</a>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,80 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
export function getWebIconData(menu) {
|
||||
const result = "/web_responsive/static/img/default_icon_app.png";
|
||||
const webIcon = menu.webIcon;
|
||||
if (webIcon && webIcon.split(",").length === 2) {
|
||||
const path = webIcon.replace(",", "/");
|
||||
return path.startsWith("/") ? path : "/" + path;
|
||||
}
|
||||
const iconData = menu.webIconData;
|
||||
if (!menu.webIcon) {
|
||||
return result;
|
||||
}
|
||||
const prefix = iconData.startsWith("P")
|
||||
? "data:image/svg+xml;base64,"
|
||||
: "data:image/png;base64,";
|
||||
if (iconData.startsWith("data:image")) {
|
||||
return iconData;
|
||||
}
|
||||
return prefix + iconData.replace(/\s/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} menu
|
||||
*/
|
||||
export function updateMenuWebIconData(menu) {
|
||||
menu.webIconData = menu.webIconData ? getWebIconData(menu) : "";
|
||||
}
|
||||
|
||||
export function updateMenuDisplayName(menu) {
|
||||
menu.displayName = menu.name.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} menu
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
export function isRootMenu(menu) {
|
||||
return menu.actionID && menu.appID === menu.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object[]} memo
|
||||
* @param {Object|null} parentMenu
|
||||
* @param {Object} menu
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
export function collectSubMenuItems(memo, parentMenu, menu) {
|
||||
const menuCopy = Object.assign({}, menu);
|
||||
updateMenuDisplayName(menuCopy);
|
||||
if (parentMenu) {
|
||||
menuCopy.displayName = `${parentMenu.displayName} / ${menuCopy.displayName}`;
|
||||
}
|
||||
if (menuCopy.actionID && !isRootMenu(menuCopy)) {
|
||||
memo.push(menuCopy);
|
||||
}
|
||||
for (const child of menuCopy.childrenTree || []) {
|
||||
collectSubMenuItems(memo, menuCopy, child);
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object[]} memo
|
||||
* @param {Object} menu
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function collectRootMenuItems(memo, menu) {
|
||||
if (isRootMenu(menu)) {
|
||||
const menuCopy = Object.assign({}, menu);
|
||||
updateMenuWebIconData(menuCopy);
|
||||
updateMenuDisplayName(menuCopy);
|
||||
memo.push(menuCopy);
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Chatter} from "@mail/chatter/web_portal/chatter";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useEffect} from "@odoo/owl";
|
||||
|
||||
patch(Chatter.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
useEffect(this._resetScrollToAttachmentsEffect.bind(this), () => [
|
||||
this.state.isAttachmentBoxOpened,
|
||||
]);
|
||||
},
|
||||
/**
|
||||
* Prevent scrollIntoView error
|
||||
* @param {Boolean} isAttachmentBoxOpened
|
||||
* @private
|
||||
*/
|
||||
_resetScrollToAttachmentsEffect(isAttachmentBoxOpened) {
|
||||
if (!isAttachmentBoxOpened) {
|
||||
this.state.scrollToAttachments = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
.o-mail-Composer {
|
||||
grid-template-areas:
|
||||
"sidebar-header core-header"
|
||||
"core-main core-main"
|
||||
"sidebar-footer core-footer";
|
||||
|
||||
.o-mail-Composer-sidebarMain {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
grid-template-areas:
|
||||
"sidebar-header core-header"
|
||||
"sidebar-main core-main"
|
||||
"sidebar-footer core-footer";
|
||||
|
||||
.o-mail-Composer-sidebarMain {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.o-mail-SuggestedRecipient {
|
||||
margin-left: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o-mail-Form-chatter {
|
||||
.o-mail-SuggestedRecipient,
|
||||
.o-mail-Chatter-recipientList {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.o-mail-SuggestedRecipient,
|
||||
.o-mail-Chatter-recipientList {
|
||||
margin-left: 42px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!-- Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<t
|
||||
t-name="web_responsive.Chatter"
|
||||
t-inherit="mail.Chatter"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<xpath
|
||||
expr="//button[hasclass('o-mail-Chatter-sendMessage')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
class="o-mail-Chatter-sendMessage btn text-nowrap me-1"
|
||||
t-att-class="{
|
||||
'btn-secondary': state.composerType !== 'message',
|
||||
'btn-primary active': state.composerType === 'message',
|
||||
'my-2': !props.compactHeight
|
||||
}"
|
||||
t-att-disabled="!state.thread.hasWriteAccess and !(state.thread.hasReadAccess and state.thread.canPostOnReadonly) and props.threadId"
|
||||
data-hotkey="m"
|
||||
t-on-click="() => this.toggleComposer('message')"
|
||||
>
|
||||
<i class="fa fa-envelope me-sm-1" />
|
||||
<span class="d-none d-sm-inline">Send message</span>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//button[hasclass('o-mail-Chatter-logNote')]" position="replace">
|
||||
<button
|
||||
class="o-mail-Chatter-logNote btn text-nowrap me-1"
|
||||
t-att-class="{
|
||||
'btn-primary active': state.composerType === 'note',
|
||||
'btn-secondary': state.composerType !== 'note',
|
||||
'my-2': !props.compactHeight
|
||||
}"
|
||||
t-att-disabled="!state.thread.hasWriteAccess and !(state.thread.hasReadAccess and state.thread.canPostOnReadonly) and props.threadId"
|
||||
data-hotkey="shift+m"
|
||||
t-on-click="() => this.toggleComposer('note')"
|
||||
>
|
||||
<i class="fa fa-sticky-note me-sm-1" />
|
||||
<span class="d-none d-sm-inline">Log note</span>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//button[hasclass('o-mail-Chatter-activity')]/span"
|
||||
position="before"
|
||||
>
|
||||
<i class="fa fa-clock-o me-sm-1" />
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//button[hasclass('o-mail-Chatter-activity')]/span"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="class" add="d-none d-sm-inline" separator=" " />
|
||||
</xpath>
|
||||
<!-- remove extra padding between the activity separator and the search message -->
|
||||
<xpath
|
||||
expr="//span[hasclass('o-mail-Chatter-topbarGrow')]"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="class" remove="pe-2" add="px-0" separator=" " />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,19 @@
|
||||
import {CommandPalette} from "@web/core/commands/command_palette";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
import {useState} from "@odoo/owl";
|
||||
|
||||
export const unpatchCommandPalette = patch(CommandPalette.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.ui = useState(useService("ui"));
|
||||
},
|
||||
|
||||
get small() {
|
||||
return this.ui.size < 2;
|
||||
},
|
||||
|
||||
get contentClass() {
|
||||
return `o_command_palette ${this.small ? "" : "mt-5"}`;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
.o_command_palette {
|
||||
.o_command_palette_exit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_command_palette_root {
|
||||
display: flex;
|
||||
max-height: 100vh;
|
||||
max-height: 100dvh;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.o_command_palette_exit {
|
||||
display: block;
|
||||
}
|
||||
.o_command_palette_search {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.o_command_palette_listbox {
|
||||
max-height: unset;
|
||||
}
|
||||
.o_command_palette_footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<templates>
|
||||
<t
|
||||
t-name="web_responsive.CommandPalette"
|
||||
t-inherit="web.CommandPalette"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<xpath expr="//Dialog" position="attributes">
|
||||
<attribute name="contentClass">contentClass</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@t-ref='root']" position="attributes">
|
||||
<attribute name="class">o_command_palette_root</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_command_palette_search')]" position="before">
|
||||
<div class="o_command_palette_exit">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary w-100"
|
||||
t-on-click="props.close"
|
||||
>Exit</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,74 @@
|
||||
/* global clearTimeout, setTimeout */
|
||||
|
||||
/* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {ControlPanel} from "@web/search/control_panel/control_panel";
|
||||
import {browser} from "@web/core/browser/browser";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
export const STICKY_CLASS = "o_mobile_sticky";
|
||||
|
||||
/**
|
||||
* @param {Number} delay
|
||||
* @returns {{collect: function(Number, (function(Number, Number): void)): void}}
|
||||
*/
|
||||
export function minMaxCollector(delay = 100) {
|
||||
const state = {
|
||||
id: null,
|
||||
items: [],
|
||||
};
|
||||
|
||||
function min() {
|
||||
return Math.min.apply(null, state.items);
|
||||
}
|
||||
|
||||
function max() {
|
||||
return Math.max.apply(null, state.items);
|
||||
}
|
||||
|
||||
return {
|
||||
collect(value, callback) {
|
||||
clearTimeout(state.id);
|
||||
state.items.push(value);
|
||||
state.id = setTimeout(() => {
|
||||
callback(min(), max());
|
||||
state.items = [];
|
||||
state.id = null;
|
||||
}, delay);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const unpatchControlPanel = patch(ControlPanel.prototype, {
|
||||
scrollValueCollector: undefined,
|
||||
/** @type {Number}*/
|
||||
scrollHeaderGap: undefined,
|
||||
setup() {
|
||||
super.setup();
|
||||
this.scrollValueCollector = minMaxCollector(100);
|
||||
this.scrollHeaderGap = 2;
|
||||
},
|
||||
onScrollThrottled() {
|
||||
if (this.isScrolling) {
|
||||
return;
|
||||
}
|
||||
this.isScrolling = true;
|
||||
browser.requestAnimationFrame(() => (this.isScrolling = false));
|
||||
|
||||
/** @type {HTMLElement}*/
|
||||
const rootEl = this.root.el;
|
||||
const scrollTop = this.getScrollingElement().scrollTop;
|
||||
const activeAnimation = scrollTop > this.initialScrollTop;
|
||||
|
||||
rootEl.classList.toggle(STICKY_CLASS, activeAnimation);
|
||||
this.scrollValueCollector.collect(scrollTop - this.oldScrollTop, (min, max) => {
|
||||
const delta = min + max;
|
||||
if (delta < -this.scrollHeaderGap || delta > this.scrollHeaderGap) {
|
||||
rootEl.style.top = `${delta < 0 ? -rootEl.clientHeight : 0}px`;
|
||||
}
|
||||
});
|
||||
|
||||
this.oldScrollTop = scrollTop;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
/* global document, window, requestAnimationFrame */
|
||||
|
||||
/* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {onMounted, onWillStart, useExternalListener, useRef} from "@odoo/owl";
|
||||
import {FileViewer} from "@web/core/file_viewer/file_viewer";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
const formChatterClassName = ".o-mail-Form-chatter";
|
||||
const formViewSheetClassName = ".o_form_view_container .o_form_sheet_bg";
|
||||
|
||||
export function useFileViewerContainerSize(ref) {
|
||||
function updateActualFormChatterSize() {
|
||||
/** @type {HTMLDivElement}*/
|
||||
const chatterElement = document.querySelector(formChatterClassName);
|
||||
/** @type {HTMLDivElement}*/
|
||||
const formSheetElement = document.querySelector(formViewSheetClassName);
|
||||
if (chatterElement && formSheetElement && ref.el) {
|
||||
/** @type {CSSStyleDeclaration}*/
|
||||
const elStyle = ref.el.style;
|
||||
const width = `${chatterElement.clientWidth}px`;
|
||||
const height = `${chatterElement.clientHeight}px`;
|
||||
const left = `${formSheetElement.clientWidth}px`;
|
||||
elStyle.setProperty("--o-FileViewerContainer-width", width);
|
||||
elStyle.setProperty("--o-FileViewerContainer-height", height);
|
||||
elStyle.setProperty("--o-FileViewerContainer-left", left);
|
||||
}
|
||||
}
|
||||
|
||||
useExternalListener(window, "resize", () => {
|
||||
requestAnimationFrame(updateActualFormChatterSize);
|
||||
});
|
||||
onMounted(() => {
|
||||
requestAnimationFrame(updateActualFormChatterSize);
|
||||
});
|
||||
}
|
||||
|
||||
export const unpatchFileViewer = patch(FileViewer.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.root = useRef("root");
|
||||
Object.assign(this.state, {
|
||||
allowMinimize: false,
|
||||
maximized: true,
|
||||
});
|
||||
useFileViewerContainerSize(this.root);
|
||||
onWillStart(this.setDefaultMaximizeState);
|
||||
},
|
||||
|
||||
get rootClass() {
|
||||
return {
|
||||
modal: this.props.modal,
|
||||
"o-FileViewerContainer__maximized": this.state.maximized,
|
||||
"o-FileViewerContainer__minimized": !this.state.maximized,
|
||||
};
|
||||
},
|
||||
|
||||
setDefaultMaximizeState() {
|
||||
this.state.allowMinimize = Boolean(
|
||||
document.querySelector(`${formChatterClassName}.o-aside`)
|
||||
);
|
||||
this.state.maximized = !this.state.allowMinimize;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Boolean} value
|
||||
*/
|
||||
setMaximized(value) {
|
||||
this.state.maximized = value;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
/* Copyright 2019 Tecnativa - Alexandre Díaz
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
.o-FileViewerContainer {
|
||||
--o-FileViewerContainer-width: #{$o-mail-Chatter-minWidth};
|
||||
--o-FileViewerContainer-height: var(--100vh, calc(100vh - #{$o-navbar-height}));
|
||||
--o-FileViewerContainer-left: unset;
|
||||
--o-FileViewerContainer-right: 0;
|
||||
|
||||
position: fixed;
|
||||
right: 0;
|
||||
z-index: $zindex-fixed;
|
||||
|
||||
&__maximized {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&__minimized {
|
||||
width: 100%;
|
||||
max-width: var(--o-FileViewerContainer-width, #{$o-mail-Chatter-minWidth});
|
||||
height: var(--o-FileViewerContainer-height);
|
||||
top: unset;
|
||||
right: var(--o-FileViewerContainer-right, 0);
|
||||
left: var(--o-FileViewerContainer-left, unset);
|
||||
bottom: 0;
|
||||
|
||||
.o-FileViewer-main {
|
||||
padding: $o-navbar-height 0 0 0;
|
||||
}
|
||||
|
||||
.o-FileViewer-viewPdf {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o-FileViewer-navigation {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
text-shadow: 0 0 rgba(30, 30, 30, 0.8);
|
||||
box-shadow: 0 0 1px 0 rgba(30, 30, 30, 0.4);
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 0 0 black;
|
||||
box-shadow: 0 0 2px 0 rgba(30, 30, 30, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_apps_menu_opened .o-FileViewerContainer {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
|
||||
Copyright 2021 Sergey Shebanin
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<template>
|
||||
<t
|
||||
t-name="web_responsive.FileViewer"
|
||||
t-inherit="web.FileViewer"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<xpath expr="div[hasclass('justify-content-center')]" position="attributes">
|
||||
<attribute name="class" add="o-FileViewerContainer" separator=" " />
|
||||
<attribute name="t-att-class">rootClass</attribute>
|
||||
<attribute name="t-ref">root</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//iframe[@t-ref='iframeViewerPdf']" position="attributes">
|
||||
<attribute name="class" add="o-FileViewer-viewPdf" separator=" " />
|
||||
</xpath>
|
||||
<xpath expr="//div[@t-on-click.stop='close']" position="before">
|
||||
<t t-if="state.allowMinimize">
|
||||
<div
|
||||
t-if="!state.maximized"
|
||||
t-on-click="setMaximized.bind(this, true)"
|
||||
class="o-FileViewer-headerButton d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer"
|
||||
role="button"
|
||||
name="maximize"
|
||||
title="Maximize"
|
||||
aria-label="Maximize"
|
||||
>
|
||||
<i class="fa fa-fw fa-window-maximize" role="img" />
|
||||
</div>
|
||||
<div
|
||||
t-if="state.maximized"
|
||||
class="o-FileViewer-headerButton d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer"
|
||||
t-on-click="setMaximized.bind(this, false)"
|
||||
role="button"
|
||||
name="minimize"
|
||||
title="Minimize"
|
||||
aria-label="Minimize"
|
||||
>
|
||||
<i class="fa fa-fw fa-window-minimize" role="img" />
|
||||
</div>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
</template>
|
||||
@@ -0,0 +1,24 @@
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
import {AttachmentList} from "@mail/core/common/attachment_list";
|
||||
|
||||
patch(AttachmentList.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
this._wr_isOpeningFileViewer = false;
|
||||
},
|
||||
|
||||
onClickAttachment(attachment) {
|
||||
// Prevent duplication for opening FileViewer within the same tick/frame
|
||||
if (this._wr_isOpeningFileViewer) {
|
||||
return;
|
||||
}
|
||||
this._wr_isOpeningFileViewer = true;
|
||||
try {
|
||||
super.onClickAttachment(attachment);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this._wr_isOpeningFileViewer = false;
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
// Shortcut table ui improvement
|
||||
.o_shortcut_table {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
max-width: 400px;
|
||||
td {
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!--
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
<templates id="template" xml:space="preserve">
|
||||
<t t-inherit="web.NavBar.SectionsMenu" t-inherit-mode="extension" owl="1">
|
||||
<xpath
|
||||
expr="//t[@t-foreach='sections']//t[@t-set='hotkey']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-value"
|
||||
>'shift+' + ((section_index + 1) % 10).toString()</attribute>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//t[@t-if='currentAppSectionsExtra.length']//t[@t-set='hotkey']"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute
|
||||
name="t-value"
|
||||
>'shift+' + (sectionsVisibleCount + 1 % 10).toString()</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,236 @@
|
||||
/* global console */
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Component, onPatched, onWillPatch, useRef, useState} from "@odoo/owl";
|
||||
import {
|
||||
collectRootMenuItems,
|
||||
collectSubMenuItems,
|
||||
} from "@web_responsive/components/apps_menu_tools.esm";
|
||||
import {useAutofocus, useService} from "@web/core/utils/hooks";
|
||||
import {debounce} from "@web/core/utils/timing";
|
||||
import {escapeRegExp} from "@web/core/utils/strings";
|
||||
import {fuzzyLookup} from "@web/core/utils/search";
|
||||
import {scrollTo} from "@web/core/utils/scrolling";
|
||||
|
||||
/**
|
||||
* @extends Component
|
||||
*/
|
||||
export class AppsMenuCanonicalSearchBar extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
rootItems: [],
|
||||
subItems: [],
|
||||
offset: 0,
|
||||
hasResults: false,
|
||||
});
|
||||
this.searchBarInput = useAutofocus({refName: "SearchBarInput"});
|
||||
this._searchMenus = debounce(this._searchMenus, 200);
|
||||
this.menuService = useService("menu");
|
||||
this.searchItemsRef = useRef("searchItems");
|
||||
this.rootMenuItems = this.getRootMenuItems();
|
||||
this.subMenuItems = this.getSubMenuItems();
|
||||
onWillPatch(this._computeResultOffset);
|
||||
onPatched(this._scrollToHighlight);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String}
|
||||
*/
|
||||
get inputValue() {
|
||||
const {el} = this.searchBarInput;
|
||||
return el ? el.value : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get hasItemsToDisplay() {
|
||||
return this.totalItemsCount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Number}
|
||||
*/
|
||||
get totalItemsCount() {
|
||||
const {rootItems, subItems} = this.state;
|
||||
return rootItems.length + subItems.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} index
|
||||
* @param {Boolean} isSubMenu
|
||||
* @returns {String}
|
||||
*/
|
||||
highlighted(index, isSubMenu = false) {
|
||||
const {state} = this;
|
||||
let _index = index;
|
||||
if (isSubMenu) {
|
||||
_index = state.rootItems.length + index;
|
||||
}
|
||||
return _index === state.offset ? "highlight" : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
getRootMenuItems() {
|
||||
return this.menuService.getApps().reduce(collectRootMenuItems, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
getSubMenuItems() {
|
||||
const response = [];
|
||||
for (const menu of this.menuService.getApps()) {
|
||||
const menuTree = this.menuService.getMenuAsTree(menu.id);
|
||||
collectSubMenuItems(response, null, menuTree);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search among available menu items, and render that search.
|
||||
*/
|
||||
_searchMenus() {
|
||||
const {state} = this;
|
||||
const query = this.inputValue;
|
||||
state.hasResults = query !== "";
|
||||
if (!state.hasResults) {
|
||||
state.rootItems = [];
|
||||
state.subItems = [];
|
||||
return;
|
||||
}
|
||||
const searchField = (item) => item.displayName;
|
||||
// Update search results paths
|
||||
for (const root in this.rootMenuItems) {
|
||||
// Root is an app
|
||||
if (this.rootMenuItems[root]?.actionPath) {
|
||||
this.rootMenuItems[root].path =
|
||||
`/odoo/${this.rootMenuItems[root].actionPath}`;
|
||||
}
|
||||
// Root is a module
|
||||
else {
|
||||
this.rootMenuItems[root].path =
|
||||
`/odoo/action-${this.rootMenuItems[root].actionID}`;
|
||||
}
|
||||
}
|
||||
for (const item in this.subMenuItems) {
|
||||
for (const root in this.rootMenuItems) {
|
||||
if (this.subMenuItems[item].appID === this.rootMenuItems[root].appID) {
|
||||
// Root is an app
|
||||
if (this.rootMenuItems[root]?.actionPath) {
|
||||
this.subMenuItems[item].path =
|
||||
`/odoo/${this.rootMenuItems[root].actionPath}/action-${this.subMenuItems[item].actionID}`;
|
||||
}
|
||||
// Root is a module
|
||||
else {
|
||||
this.subMenuItems[item].path =
|
||||
`/odoo/action-${this.subMenuItems[item].actionID}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
state.rootItems = fuzzyLookup(query, this.rootMenuItems, searchField);
|
||||
state.subItems = fuzzyLookup(query, this.subMenuItems, searchField);
|
||||
}
|
||||
|
||||
_onKeyDown(ev) {
|
||||
const code = ev.code;
|
||||
if (code === "Escape") {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
if (this.inputValue) {
|
||||
this.searchBarInput.el.value = "";
|
||||
Object.assign(this.state, {rootItems: [], subItems: []});
|
||||
this.state.hasResults = false;
|
||||
} else {
|
||||
this.env.bus.trigger("ACTION_MANAGER:UI-UPDATED");
|
||||
}
|
||||
} else if (code === "Tab") {
|
||||
if (this.searchItemsRef.el) {
|
||||
ev.preventDefault();
|
||||
if (ev.shiftKey) {
|
||||
this.state.offset--;
|
||||
} else {
|
||||
this.state.offset++;
|
||||
}
|
||||
}
|
||||
} else if (code === "ArrowUp") {
|
||||
if (this.searchItemsRef.el) {
|
||||
ev.preventDefault();
|
||||
this.state.offset--;
|
||||
}
|
||||
} else if (code === "ArrowDown") {
|
||||
if (this.searchItemsRef.el) {
|
||||
ev.preventDefault();
|
||||
this.state.offset++;
|
||||
}
|
||||
} else if (code === "Enter") {
|
||||
const element = this.searchItemsRef.el;
|
||||
if (this.hasItemsToDisplay && element) {
|
||||
ev.preventDefault();
|
||||
this._selectHighlightedSearchItem(element);
|
||||
}
|
||||
} else if (code === "Home") {
|
||||
this.state.offset = 0;
|
||||
} else if (code === "End") {
|
||||
this.state.offset = this.totalItemsCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} element
|
||||
* @private
|
||||
*/
|
||||
_selectHighlightedSearchItem(element) {
|
||||
const highlightedElement = element.querySelector(
|
||||
".highlight > .search-item__link"
|
||||
);
|
||||
if (highlightedElement) {
|
||||
highlightedElement.click();
|
||||
} else {
|
||||
console.warn("Highlighted search item is not found");
|
||||
}
|
||||
}
|
||||
|
||||
_splitName(name) {
|
||||
if (!name) {
|
||||
return [];
|
||||
}
|
||||
const value = this.inputValue;
|
||||
const splitName = name.split(new RegExp(`(${escapeRegExp(value)})`, "ig"));
|
||||
return value.length && splitName.length > 1 ? splitName : [name];
|
||||
}
|
||||
|
||||
_scrollToHighlight() {
|
||||
// Scroll to selected element on keyboard navigation
|
||||
const element = this.searchItemsRef.el;
|
||||
if (!(this.totalItemsCount && element)) {
|
||||
return;
|
||||
}
|
||||
const activeElement = element.querySelector(".highlight");
|
||||
if (activeElement) {
|
||||
scrollTo(activeElement, element);
|
||||
}
|
||||
}
|
||||
|
||||
_computeResultOffset() {
|
||||
// Allow looping on results
|
||||
const {state} = this;
|
||||
const total = this.totalItemsCount;
|
||||
if (state.offset < 0) {
|
||||
state.offset = total + state.offset;
|
||||
} else if (state.offset >= total) {
|
||||
state.offset -= total;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppsMenuCanonicalSearchBar.props = {};
|
||||
AppsMenuCanonicalSearchBar.template = "web_responsive.AppsMenuCanonicalSearchBar";
|
||||
@@ -0,0 +1,112 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
:root {
|
||||
.o_grid_apps_menu[data-theme="milk"] {
|
||||
--apps-menu-scrollbar-background: #{$o-brand-odoo};
|
||||
--apps-menu-empty-search-color: $app-menu-text-color;
|
||||
}
|
||||
|
||||
.o_grid_apps_menu[data-theme="community"] {
|
||||
--apps-menu-scrollbar-background: white;
|
||||
--apps-menu-empty-search-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.o_grid_apps_menu .search-container {
|
||||
// Allow to scroll only on results, keeping static search box above
|
||||
.search-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(0.25rem + 1px);
|
||||
overflow: auto;
|
||||
padding: 0.25rem 0;
|
||||
margin: 0.25rem 0;
|
||||
max-height: calc(100vh - #{$o-navbar-height} - 5.25rem);
|
||||
max-height: calc(100dvh - #{$o-navbar-height} - 5.25rem);
|
||||
max-width: calc(100vw - 1rem);
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--apps-menu-scrollbar-background);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-item-divider {
|
||||
margin: 0 4px;
|
||||
|
||||
hr {
|
||||
margin: 0.5rem 0;
|
||||
background-color: $o-brand-odoo;
|
||||
}
|
||||
}
|
||||
|
||||
.search-item {
|
||||
display: block;
|
||||
align-items: center;
|
||||
background-position: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
white-space: normal;
|
||||
font-weight: 100;
|
||||
background-color: white;
|
||||
box-shadow: $app-menu-box-shadow;
|
||||
margin: 0 4px;
|
||||
border-radius: 4px;
|
||||
|
||||
&__link {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__name {
|
||||
color: $app-menu-text-color;
|
||||
text-shadow: 0 0 $app-menu-text-color;
|
||||
}
|
||||
|
||||
&__image {
|
||||
max-height: 40px;
|
||||
max-width: 40px;
|
||||
width: 40px;
|
||||
object-fit: contain;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&.highlight,
|
||||
&:hover {
|
||||
background-color: $app-menu-item-highlight;
|
||||
box-shadow: $app-menu-box-shadow-highlight;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-search-item {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 0.25rem 0.5rem;
|
||||
color: var(--apps-menu-empty-search-color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Onestein - Anjeel Haria
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<!-- Search bar -->
|
||||
<t t-name="web_responsive.AppsMenuCanonicalSearchBar">
|
||||
<div class="search-container" t-att-class="{'has-results': state.hasResults}">
|
||||
<div class="search-input">
|
||||
<i class="fa fa-search search-icon fs-4 my-auto d-none d-sm-flex" />
|
||||
<input
|
||||
type="search"
|
||||
t-ref="SearchBarInput"
|
||||
t-on-input="_searchMenus"
|
||||
t-on-keydown="_onKeyDown"
|
||||
autocomplete="off"
|
||||
placeholder="Search menus..."
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
<ul
|
||||
t-if="hasItemsToDisplay"
|
||||
class="list-unstyled search-list"
|
||||
t-ref="searchItems"
|
||||
>
|
||||
<t t-foreach="state.rootItems" t-as="menu" t-key="menu.id">
|
||||
<li t-attf-class="search-item {{highlighted(menu_index)}}">
|
||||
<a
|
||||
t-attf-class="search-item__link"
|
||||
t-att-href="menu.path"
|
||||
t-att-data-menu-id="menu.id"
|
||||
t-att-data-action-id="menu.actionID"
|
||||
draggable="false"
|
||||
tabindex="-1"
|
||||
>
|
||||
<img
|
||||
class="search-item__image"
|
||||
t-att-src="menu.webIconData"
|
||||
alt="App Icon"
|
||||
/>
|
||||
<span class="search-item__name" t-att-title="menu.name">
|
||||
<t
|
||||
t-foreach="_splitName(menu.displayName)"
|
||||
t-as="name"
|
||||
t-key="name_index"
|
||||
>
|
||||
<b t-if="name_index % 2" t-out="name" />
|
||||
<t t-else="" t-out="name" />
|
||||
</t>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
<li
|
||||
class="search-item-divider"
|
||||
t-if="state.rootItems.length and state.subItems.length"
|
||||
>
|
||||
<hr class="w-100" />
|
||||
</li>
|
||||
<t t-foreach="state.subItems" t-as="menu" t-key="menu.id">
|
||||
<li t-attf-class="search-item {{highlighted(menu_index, true)}}">
|
||||
<a
|
||||
t-attf-class="search-item__link"
|
||||
t-att-href="menu.path"
|
||||
t-att-data-menu-id="menu.id"
|
||||
t-att-data-action-id="menu.actionID"
|
||||
draggable="false"
|
||||
tabindex="-1"
|
||||
>
|
||||
<span
|
||||
class="search-item__name px-2 py-1"
|
||||
t-att-title="menu.name"
|
||||
>
|
||||
<t
|
||||
t-foreach="_splitName(menu.displayName)"
|
||||
t-as="name"
|
||||
t-key="name_index"
|
||||
>
|
||||
<b t-if="name_index % 2" t-out="name" />
|
||||
<t t-else="" t-out="name" />
|
||||
</t>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
<ul
|
||||
t-if="!hasItemsToDisplay and inputValue"
|
||||
class="list-unstyled search-list"
|
||||
>
|
||||
<li class="empty-search-item">
|
||||
<strong>Nothing to show</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,31 @@
|
||||
/* global Fuse */
|
||||
/* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {AppsMenuCanonicalSearchBar} from "@web_responsive/components/menu_canonical_searchbar/searchbar.esm";
|
||||
|
||||
/**
|
||||
* @extends AppsMenuCanonicalSearchBar
|
||||
*/
|
||||
export class AppsMenuFuseSearchBar extends AppsMenuCanonicalSearchBar {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.fuseOptions = {
|
||||
keys: ["displayName"],
|
||||
threshold: 0.43,
|
||||
};
|
||||
this.rootMenuItems = new Fuse(this.getRootMenuItems(), this.fuseOptions);
|
||||
this.subMenuItems = new Fuse(this.getSubMenuItems(), this.fuseOptions);
|
||||
}
|
||||
|
||||
_searchMenus() {
|
||||
const {state} = this;
|
||||
const query = this.inputValue;
|
||||
state.hasResults = query !== "";
|
||||
state.rootItems = this.rootMenuItems.search(query);
|
||||
state.subItems = this.subMenuItems.search(query);
|
||||
}
|
||||
}
|
||||
|
||||
AppsMenuFuseSearchBar.props = {};
|
||||
AppsMenuFuseSearchBar.template = "web_responsive.AppsMenuFuseSearchBar";
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<!-- Search bar -->
|
||||
<t
|
||||
t-name="web_responsive.AppsMenuFuseSearchBar"
|
||||
t-inherit="web_responsive.AppsMenuCanonicalSearchBar"
|
||||
t-inherit-mode="primary"
|
||||
>
|
||||
<xpath expr="//t[@t-foreach='state.rootItems']" position="attributes">
|
||||
<attribute name="t-as">result</attribute>
|
||||
<attribute name="t-key">result.item.xmlid</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-foreach='state.rootItems']/li" position="before">
|
||||
<t t-set="menu" t-value="result.item" />
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-foreach='state.rootItems']/li" position="attributes">
|
||||
<attribute
|
||||
name="t-attf-class"
|
||||
>search-item {{highlighted(result_index)}}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-foreach='state.subItems']" position="attributes">
|
||||
<attribute name="t-as">result</attribute>
|
||||
<attribute name="t-key">result.item.xmlid</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-foreach='state.subItems']/li" position="before">
|
||||
<t t-set="menu" t-value="result.item" />
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-foreach='state.subItems']/li" position="attributes">
|
||||
<attribute
|
||||
name="t-attf-class"
|
||||
>search-item {{highlighted(result_index, true)}}</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,64 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Onestein - Anjeel Haria
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {Component, useState} from "@odoo/owl";
|
||||
import {useAutofocus, useService} from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* @extends Component
|
||||
* @property {{el: HTMLInputElement}} searchBarInput
|
||||
*/
|
||||
export class AppsMenuOdooSearchBar extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.state = useState({
|
||||
rootItems: [],
|
||||
subItems: [],
|
||||
offset: 0,
|
||||
hasResults: false,
|
||||
});
|
||||
this.searchBarInput = useAutofocus({refName: "SearchBarInput"});
|
||||
this.command = useService("command");
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {String}
|
||||
*/
|
||||
get inputValue() {
|
||||
const {el} = this.searchBarInput;
|
||||
return el ? el.value : "";
|
||||
}
|
||||
|
||||
set inputValue(value) {
|
||||
const {el} = this.searchBarInput;
|
||||
if (el) {
|
||||
el.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
_onSearchInput() {
|
||||
if (this.inputValue) {
|
||||
this._openSearchMenu(this.inputValue);
|
||||
this.inputValue = "";
|
||||
}
|
||||
}
|
||||
|
||||
_onSearchClick() {
|
||||
this._openSearchMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} [value]
|
||||
* @private
|
||||
*/
|
||||
_openSearchMenu(value) {
|
||||
const searchValue = value ? `/${value}` : "/";
|
||||
this.command.openMainPalette({searchValue}, null);
|
||||
}
|
||||
}
|
||||
|
||||
AppsMenuOdooSearchBar.props = {};
|
||||
AppsMenuOdooSearchBar.template = "web_responsive.AppsMenuOdooSearchBar";
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Onestein - Anjeel Haria
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<!-- Search bar -->
|
||||
<t t-name="web_responsive.AppsMenuOdooSearchBar">
|
||||
<div class="search-container">
|
||||
<div class="search-input">
|
||||
<i class="fa fa-search search-icon fs-4 my-auto d-none d-sm-flex" />
|
||||
<input
|
||||
type="search"
|
||||
t-ref="SearchBarInput"
|
||||
t-on-input="_onSearchInput"
|
||||
t-on-click="_onSearchClick"
|
||||
autocomplete="off"
|
||||
placeholder="Search menus..."
|
||||
class="form-control"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -0,0 +1,25 @@
|
||||
/* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
import {AppsMenuCanonicalSearchBar} from "@web_responsive/components/menu_canonical_searchbar/searchbar.esm";
|
||||
import {AppsMenuFuseSearchBar} from "@web_responsive/components/menu_fuse_searchbar/searchbar.esm";
|
||||
import {AppsMenuOdooSearchBar} from "@web_responsive/components/menu_odoo_searchbar/searchbar.esm";
|
||||
import {Component} from "@odoo/owl";
|
||||
import {session} from "@web/session";
|
||||
|
||||
export class AppsMenuSearchBar extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.searchType = session.apps_menu.search_type || "canonical";
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(AppsMenuSearchBar, {
|
||||
props: {},
|
||||
template: "web_responsive.AppsMenuSearchBar",
|
||||
components: {
|
||||
AppsMenuOdooSearchBar,
|
||||
AppsMenuCanonicalSearchBar,
|
||||
AppsMenuFuseSearchBar,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
.o_grid_apps_menu .search-container {
|
||||
width: 100%;
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
gap: 0.75rem;
|
||||
box-shadow: $app-menu-box-shadow;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background-color: white;
|
||||
|
||||
.search-icon {
|
||||
color: $app-menu-text-color;
|
||||
font-size: 1.5rem;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
height: 1.75rem;
|
||||
background: none;
|
||||
border: none;
|
||||
color: $app-menu-text-color;
|
||||
display: block;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
|
||||
&::placeholder {
|
||||
color: $app-menu-text-color;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_command_palette_search .form-control {
|
||||
&:focus {
|
||||
box-shadow: unset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
Copyright 2021 ITerra - Sergey Shebanin
|
||||
Copyright 2023 Onestein - Anjeel Haria
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
<templates>
|
||||
<!-- Search bar -->
|
||||
<t t-name="web_responsive.AppsMenuSearchBar">
|
||||
<AppsMenuCanonicalSearchBar t-if="searchType==='canonical'" />
|
||||
<AppsMenuOdooSearchBar t-if="searchType==='command_palette'" />
|
||||
<AppsMenuFuseSearchBar t-if="searchType==='fuse'" />
|
||||
</t>
|
||||
</templates>
|
||||
Reference in New Issue
Block a user