mirror of
https://github.com/OCA/web.git
synced 2025-02-22 13:21:25 +02:00
[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:
committed by
Taras Shabaranskyi
parent
fa5e79acc1
commit
475d01f68a
@@ -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});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 ? "background-image:url('data:image/png;base64," + menu.webIconData + "')" : ''"
|
||||
t-att-style="menu.webIconData ? "background-image:url(" + menu.webIconData + ");background-size:4%" : ''"
|
||||
t-attf-href="#menu_id={{menu.id}}&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>
|
||||
|
||||
Reference in New Issue
Block a user