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

This commit is contained in:
2026-05-14 17:52:50 +00:00
parent 0bba5fcf42
commit 2f7f2e42a4
111 changed files with 10206 additions and 0 deletions

View File

@@ -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");
},
});

View File

@@ -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%);
}

View File

@@ -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>

View File

@@ -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});