-
-
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/web_responsive/static/src/components/apps_menu/apps_menu_preferences.esm.js b/web_responsive/static/src/components/apps_menu/apps_menu_preferences.esm.js
new file mode 100644
index 000000000..9f6678ff4
--- /dev/null
+++ b/web_responsive/static/src/components/apps_menu/apps_menu_preferences.esm.js
@@ -0,0 +1,39 @@
+/** @odoo-module **/
+/* 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";
+
+class AppsMenuPreferences extends Component {
+ setup() {
+ this.action = useService("action");
+ this.user = useService("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`
+
+
+
+
+
+`;
+
+registry
+ .category("systray")
+ .add("AppMenuTheme", {Component: AppsMenuPreferences}, {sequence: 100});
diff --git a/web_responsive/static/src/components/apps_menu_item/apps_menu_item.esm.js b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.esm.js
new file mode 100644
index 000000000..70b271240
--- /dev/null
+++ b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.esm.js
@@ -0,0 +1,53 @@
+/** @odoo-module **/
+/* 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,
+ },
+});
diff --git a/web_responsive/static/src/components/apps_menu_item/apps_menu_item.scss b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.scss
new file mode 100644
index 000000000..029a0df29
--- /dev/null
+++ b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.scss
@@ -0,0 +1,73 @@
+/* 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);
+ }
+}
diff --git a/web_responsive/static/src/components/apps_menu_item/apps_menu_item.xml b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.xml
new file mode 100644
index 000000000..a25834bc7
--- /dev/null
+++ b/web_responsive/static/src/components/apps_menu_item/apps_menu_item.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/apps_menu_tools.esm.js b/web_responsive/static/src/components/apps_menu_tools.esm.js
new file mode 100644
index 000000000..6749dd71a
--- /dev/null
+++ b/web_responsive/static/src/components/apps_menu_tools.esm.js
@@ -0,0 +1,77 @@
+/** @odoo-module **/
+
+/* 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 iconData = menu.webIconData;
+ if (!iconData) {
+ 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;
+}
diff --git a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.esm.js b/web_responsive/static/src/components/attachment_viewer/attachment_viewer.esm.js
deleted file mode 100644
index 52068df25..000000000
--- a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.esm.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/** @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";
-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", {
- setup() {
- this._super();
- this.state = useState({
- maximized: 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();
- },
- },
- },
-});
diff --git a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.scss b/web_responsive/static/src/components/attachment_viewer/attachment_viewer.scss
deleted file mode 100644
index c92690adb..000000000
--- a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.scss
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Copyright 2019 Tecnativa - Alexandre Díaz
- * Copyright 2021 ITerra - Sergey Shebanin
- * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
-
-// Attachment Viewer
-.o_web_client .o_DialogManager_dialog {
- /* Show sided viewer on large screens */
- @media (min-width: 1533px) {
- &:not(:has(.o_AttachmentDeleteConfirm)) {
- position: static;
- }
- .o_AttachmentViewer_main {
- padding-bottom: 20px;
- }
- .o_AttachmentViewer {
- // On-top of navbar
- z-index: 10;
- position: absolute;
- right: 0;
- top: 0;
- bottom: 0;
- margin-left: auto;
- background-color: rgba(0, 0, 0, 0.7);
-
- width: $chatter_zone_width;
- &.o_AttachmentViewer_maximized {
- width: 100% !important;
- }
-
- /* Show/Hide control buttons (next, prev, etc..) */
- &:hover .o_AttachmentViewer_buttonNavigation,
- &:hover .o_AttachmentViewer_toolbar {
- display: flex;
- }
- .o_AttachmentViewer_buttonNavigation,
- .o_AttachmentViewer_toolbar {
- display: none;
- }
- .o_AttachmentViewer_viewIframe {
- width: 95%;
- }
- }
- }
- @media (max-width: 1533px) {
- .o_AttachmentViewer_headerItemButtonMinimize,
- .o_AttachmentViewer_headerItemButtonMaximize {
- display: none !important;
- }
- }
-}
-/* Attachment Viewer Max/Min buttons only are useful in sided mode */
-.o_FormRenderer_chatterContainer:not(.o-aside) {
- .o_AttachmentViewer_headerItemButtonMinimize,
- .o_AttachmentViewer_headerItemButtonMaximize {
- display: none !important;
- }
-}
-
-.o_apps_menu_opened .o_AttachmentViewer {
- display: none !important;
-}
diff --git a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.xml b/web_responsive/static/src/components/attachment_viewer/attachment_viewer.xml
deleted file mode 100644
index 57f3b6c99..000000000
--- a/web_responsive/static/src/components/attachment_viewer/attachment_viewer.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
- state.maximized ? 'o_AttachmentViewer_maximized' : ''
-
-
-
-
-
-
-
diff --git a/web_responsive/static/src/components/chatter/chatter.esm.js b/web_responsive/static/src/components/chatter/chatter.esm.js
new file mode 100644
index 000000000..b12fc7ae0
--- /dev/null
+++ b/web_responsive/static/src/components/chatter/chatter.esm.js
@@ -0,0 +1,28 @@
+/** @odoo-module **/
+/* 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/core/web/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;
+ }
+ },
+});
diff --git a/web_responsive/static/src/components/chatter/chatter.scss b/web_responsive/static/src/components/chatter/chatter.scss
new file mode 100644
index 000000000..6ea395863
--- /dev/null
+++ b/web_responsive/static/src/components/chatter/chatter.scss
@@ -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;
+ }
+ }
+}
diff --git a/web_responsive/static/src/components/chatter/chatter.xml b/web_responsive/static/src/components/chatter/chatter.xml
new file mode 100644
index 000000000..689004572
--- /dev/null
+++ b/web_responsive/static/src/components/chatter/chatter.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+ ''
+
+
+
+
+
+
+
+
+ Send message
+
+
+
+
+
+ Log note
+
+
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/chatter_topbar/chatter_topbar.esm.js b/web_responsive/static/src/components/chatter_topbar/chatter_topbar.esm.js
deleted file mode 100644
index b4140a11f..000000000
--- a/web_responsive/static/src/components/chatter_topbar/chatter_topbar.esm.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/** @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;
- },
-});
diff --git a/web_responsive/static/src/components/chatter_topbar/chatter_topbar.xml b/web_responsive/static/src/components/chatter_topbar/chatter_topbar.xml
deleted file mode 100644
index a36780f16..000000000
--- a/web_responsive/static/src/components/chatter_topbar/chatter_topbar.xml
+++ /dev/null
@@ -1,223 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Send message
-
-
- Log note
-
-
-
-
-
-
-
-
diff --git a/web_responsive/static/src/components/command_palette/main.esm.js b/web_responsive/static/src/components/command_palette/main.esm.js
new file mode 100644
index 000000000..a23fe795e
--- /dev/null
+++ b/web_responsive/static/src/components/command_palette/main.esm.js
@@ -0,0 +1,21 @@
+/** @odoo-module **/
+
+import {useState} from "@odoo/owl";
+import {useService} from "@web/core/utils/hooks";
+import {CommandPalette} from "@web/core/commands/command_palette";
+import {patch} from "@web/core/utils/patch";
+
+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"}`;
+ },
+});
diff --git a/web_responsive/static/src/components/command_palette/main.scss b/web_responsive/static/src/components/command_palette/main.scss
new file mode 100644
index 000000000..834b8b8db
--- /dev/null
+++ b/web_responsive/static/src/components/command_palette/main.scss
@@ -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;
+ }
+ }
+}
diff --git a/web_responsive/static/src/components/command_palette/main.xml b/web_responsive/static/src/components/command_palette/main.xml
new file mode 100644
index 000000000..a49723e7e
--- /dev/null
+++ b/web_responsive/static/src/components/command_palette/main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+ contentClass
+
+
+ o_command_palette_root
+
+
+
+ Exit
+
+
+
+
diff --git a/web_responsive/static/src/components/control_panel/control_panel.esm.js b/web_responsive/static/src/components/control_panel/control_panel.esm.js
index f0c774f23..b1bfb4dca 100644
--- a/web_responsive/static/src/components/control_panel/control_panel.esm.js
+++ b/web_responsive/static/src/components/control_panel/control_panel.esm.js
@@ -1,45 +1,73 @@
/** @odoo-module **/
-/* 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 LegacyControlPanel from "web.ControlPanel";
import {ControlPanel} from "@web/search/control_panel/control_panel";
-import {deviceContext} from "@web_responsive/components/ui_context.esm";
-import {patch} from "web.utils";
-import {Dropdown} from "@web/core/dropdown/dropdown";
+import {patch} from "@web/core/utils/patch";
+import {browser} from "@web/core/browser/browser";
-const {useState} = owl;
+export const STICKY_CLASS = "o_mobile_sticky";
-// 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.
+/**
+ * @param {Number} delay
+ * @returns {{collect: function(Number, (function(Number, Number): void)): void}}
+ */
+export function minMaxCollector(delay = 100) {
+ const state = {
+ id: null,
+ items: [],
+ };
-// Patch legacy control panel to add states for mobile quick search
-patch(LegacyControlPanel.prototype, "web_responsive.LegacyControlPanelMobile", {
+ 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() {
- this._super();
- this.state = useState({
- mobileSearchMode: this.props.withBreadcrumbs ? "" : "quick",
- });
- this.ui = deviceContext;
+ super.setup();
+ this.scrollValueCollector = minMaxCollector(100);
+ this.scrollHeaderGap = 2;
},
- setMobileSearchMode(ev) {
- this.state.mobileSearchMode = ev.detail;
+ 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;
},
});
-
-// Patch control panel to add states for mobile quick search
-patch(ControlPanel.prototype, "web_responsive.ControlPanelMobile", {
- setup() {
- this._super();
- this.state = useState({
- mobileSearchMode: "",
- });
- this.ui = deviceContext;
- },
- setMobileSearchMode(ev) {
- this.state.mobileSearchMode = ev.detail;
- },
-});
-
-Object.assign(LegacyControlPanel.components, {Dropdown});
diff --git a/web_responsive/static/src/components/control_panel/control_panel.scss b/web_responsive/static/src/components/control_panel/control_panel.scss
deleted file mode 100644
index 2b7b4e409..000000000
--- a/web_responsive/static/src/components/control_panel/control_panel.scss
+++ /dev/null
@@ -1,306 +0,0 @@
-/* Copyright 2018 Tecnativa - Jairo Llopis
- * Copyright 2021 ITerra - Sergey Shebanin
- * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
-
-// Make enough space for search panel filters buttons
-.o_control_panel {
- // There is no media breakpoint for XL upper bound
- @include media-breakpoint-up(lg) {
- @media (max-width: 1360px) {
- .o_cp_top_left,
- .o_cp_bottom_left {
- width: 40%;
- }
- .o_cp_top_right,
- .o_cp_bottom_right {
- width: 60%;
- }
- }
- }
- // For FULL HD devices
- @media (min-width: 1900px) {
- .o_cp_top_left,
- .o_cp_bottom_left {
- width: 60%;
- }
- .o_cp_top_right,
- .o_cp_bottom_right {
- width: 40%;
- }
- }
- @include media-breakpoint-only(md) {
- .o_search_options_hide_labels .o_dropdown_title {
- display: none;
- }
- }
- .o_cp_bottom_right {
- height: 10%;
- }
- // Mobile Control panel (breadcrumbs, search box, buttons...)
- @include media-breakpoint-down(sm) {
- // Avoid horizontal scrolling of control panel.
- // It doesn't work on iOS Safari, but it looks similar as
- // without this patch. With this patch it looks better for
- // other browsers.
-
- // Arrange buttons to use space better
- .o_cp_top_left,
- .o_cp_top_right {
- flex: 1 1 100%;
- }
-
- .o_cp_top_left {
- flex-basis: 89%;
- max-width: 89%;
- }
-
- .o_cp_top_right {
- 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;
- }
-
- .o_cp_bottom_right {
- float: right;
- padding-left: 10px;
- margin: 5px 0;
- }
-
- .o_cp_bottom_right,
- .o_cp_pager {
- white-space: nowrap;
- }
- .o_cp_pager {
- margin-bottom: 0;
- }
- .o_list_selection_box {
- padding-left: 5px !important;
- padding-right: 5px;
- }
-
- .o_cp_action_menus {
- padding-right: 0;
- .o_dropdown_title,
- .fa-chevron-right,
- .fa-chevron-down {
- display: none;
- }
- .dropdown-toggle {
- margin: 0px 2px;
- height: 100%;
- }
- .dropdown {
- height: 100%;
- }
- @include media-breakpoint-down(xs) {
- .dropdown {
- position: static;
- }
- .dropdown-menu {
- right: 0;
- left: 0;
- top: 35px;
- }
- }
- }
-
- // Hide all but 2 last breadcrumbs, and render 2nd-to-last as arrow
- .breadcrumb-item {
- &:not(.active):not(.o_back_button) {
- padding-left: 0;
- display: none;
- }
-
- &::before {
- content: none;
- padding-right: 0;
- }
-
- &.o_back_button {
- &::before {
- color: var(--primary);
- content: "\f060"; // .fa-arrow-left
- cursor: pointer;
- font-family: FontAwesome;
- }
-
- a {
- display: none;
- }
- }
- }
-
- // Ellipsize long breadcrumbs
- .breadcrumb {
- max-width: 100%;
- text-overflow: ellipsis;
- }
-
- // In case you install `mail`, there is a mess on BS vs inline styles
- // we need to fix
- .o_cp_buttons .btn.d-block:not(.d-none) {
- display: inline-block !important;
- }
-
- .o_searchview_input_container > .o_searchview_autocomplete {
- left: 0;
- right: 0;
- > li {
- padding: 10px 0px;
- }
- }
- .o_searchview_quick {
- display: flex;
- flex: 1 1 auto;
- align-items: center;
- .o_searchview_input_container {
- flex: 1 1 auto;
- margin-left: 5px;
- }
- }
- .o_searchview {
- padding: 1px 0px 3px 0px;
- &.o_searchview_mobile {
- cursor: pointer;
- }
- }
- }
- // Filter Menu
- // Cut long filters names in the filters menu
- .o_filter_menu {
- .o_menu_item {
- @include media-breakpoint-up(md) {
- max-width: 250px;
- }
- a {
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
- }
- // Enable scroll on dropdowns
- .o_cp_buttons .dropdown-menu {
- max-height: 70vh;
- overflow-y: auto;
- overflow-x: hidden;
- }
- // Dropdown with buttons to switch the view type
- .o_cp_switch_buttons.dropdown-menu {
- align-content: center;
- display: flex;
- flex-direction: row;
- justify-content: space-around;
- padding: 0;
-
- .btn {
- border: {
- bottom: 0;
- radius: 0;
- top: 0;
- }
- font-size: 1.3em;
- }
- }
-}
-// Mobile search bar full screen mode
-.o_cp_mobile_search {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- padding: 0;
- width: 100%;
- background-color: white;
- z-index: $zindex-modal;
- overflow: auto;
- .o_mobile_search_header {
- background-color: var(--mobileSearch__header-bg, #{$o-brand-odoo});
- display: flex;
- min-height: $o-navbar-height;
- justify-content: space-between;
- width: 100%;
-
- .o_mobile_search_button {
- color: white;
-
- &:active {
- background-color: darken($o-brand-primary, 10%);
- }
- }
- }
- .o_searchview_input_container {
- display: flex;
- padding: 15px 20px 0 20px;
- position: relative;
- .o_searchview_input {
- width: 100%;
- margin-bottom: 15px;
- border-bottom: 1px solid $o-brand-primary;
- }
- .o_searchview_facet {
- display: inline-flex;
- order: 1;
- }
- .o_searchview_autocomplete {
- top: 3rem;
- }
- }
- .o_mobile_search_filter {
- padding-bottom: 15%;
- > .dropdown {
- flex-direction: column;
- line-height: 2rem;
- width: 100%;
- margin: 15px 5px 0px 5px;
- border: solid 1px darken($gray-200, 20%);
- }
- .dropdown.show > .dropdown-toggle {
- background-color: $gray-200;
- }
- .dropdown-toggle {
- width: 100%;
- text-align: left;
- &:after {
- top: auto;
- }
- }
- .dropdown-item:before {
- top: auto;
- }
- .dropdown-item.focus {
- background-color: white;
- }
- .dropdown-menu {
- // Here we use !important because of popper js adding custom style
- // to element so to override it use !important
- position: relative !important;
- top: 0 !important;
- left: 0 !important;
- width: 100%;
- max-height: 100%;
- box-shadow: none;
- border: none;
- color: $gray-600;
- .divider {
- margin: 0px;
- }
- > li > a {
- padding: 10px 26px;
- }
- }
- }
- .o_mobile_search_show_result {
- padding: 10px;
- font-size: 15px;
- }
-}
diff --git a/web_responsive/static/src/components/control_panel/control_panel.xml b/web_responsive/static/src/components/control_panel/control_panel.xml
deleted file mode 100644
index 5ecbdbfa2..000000000
--- a/web_responsive/static/src/components/control_panel/control_panel.xml
+++ /dev/null
@@ -1,227 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ui.isSmall and state.mobileSearchMode == 'quick' ? 'o_hidden' : ''
-
-
- !ui.isSmall
- ui.size == ui.SIZES.MD ? 'o_search_options_hide_labels' : ''
-
-
-
-
-
-
-
-
-
-
-
-
-
- SEE RESULT
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- SEE RESULT
-
-
-
-
-
diff --git a/web_responsive/static/src/components/file_viewer/file_viewer.esm.js b/web_responsive/static/src/components/file_viewer/file_viewer.esm.js
new file mode 100644
index 000000000..145a84445
--- /dev/null
+++ b/web_responsive/static/src/components/file_viewer/file_viewer.esm.js
@@ -0,0 +1,77 @@
+/** @odoo-module **/
+/* 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);
+ });
+}
+
+/**
+ * Patch attachment viewer to add min/max buttons capability
+ * @property {Function} resizeUpdateActualFormChatterWidth
+ */
+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;
+ },
+});
diff --git a/web_responsive/static/src/components/file_viewer/file_viewer.scss b/web_responsive/static/src/components/file_viewer/file_viewer.scss
new file mode 100644
index 000000000..f528e8333
--- /dev/null
+++ b/web_responsive/static/src/components/file_viewer/file_viewer.scss
@@ -0,0 +1,56 @@
+/* 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;
+}
diff --git a/web_responsive/static/src/components/file_viewer/file_viewer.xml b/web_responsive/static/src/components/file_viewer/file_viewer.xml
new file mode 100644
index 000000000..ace2e1d10
--- /dev/null
+++ b/web_responsive/static/src/components/file_viewer/file_viewer.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+ rootClass
+ root
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/hotkey/hotkey.xml b/web_responsive/static/src/components/hotkey/hotkey.xml
index f7a484ca0..d9b615c92 100644
--- a/web_responsive/static/src/components/hotkey/hotkey.xml
+++ b/web_responsive/static/src/components/hotkey/hotkey.xml
@@ -1,6 +1,7 @@
@@ -11,7 +12,6 @@
>
'shift+' + ((section_index + 1) % 10).toString()
'shift+' + (sectionsVisibleCount + 1 % 10).toString()
diff --git a/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.esm.js b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.esm.js
new file mode 100644
index 000000000..265778e43
--- /dev/null
+++ b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.esm.js
@@ -0,0 +1,207 @@
+/** @odoo-module **/
+/* 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;
+ 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";
diff --git a/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.scss b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.scss
new file mode 100644
index 000000000..e8d9be180
--- /dev/null
+++ b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.scss
@@ -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);
+ }
+}
diff --git a/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.xml b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.xml
new file mode 100644
index 000000000..023864a0f
--- /dev/null
+++ b/web_responsive/static/src/components/menu_canonical_searchbar/searchbar.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.esm.js b/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.esm.js
new file mode 100644
index 000000000..3aa7da63e
--- /dev/null
+++ b/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.esm.js
@@ -0,0 +1,32 @@
+/** @odoo-module **/
+/* 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";
diff --git a/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.xml b/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.xml
new file mode 100644
index 000000000..50945226c
--- /dev/null
+++ b/web_responsive/static/src/components/menu_fuse_searchbar/searchbar.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ result
+ result.item.xmlid
+
+
+
+
+
+ search-item {{highlighted(result_index)}}
+
+
+ result
+ result.item.xmlid
+
+
+
+
+
+ search-item {{highlighted(result_index, true)}}
+
+
+
diff --git a/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.esm.js b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.esm.js
new file mode 100644
index 000000000..c0b5e7b9b
--- /dev/null
+++ b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.esm.js
@@ -0,0 +1,65 @@
+/** @odoo-module **/
+/* 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";
diff --git a/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.scss b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.scss
new file mode 100644
index 000000000..b503e575b
--- /dev/null
+++ b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.scss
@@ -0,0 +1,4 @@
+/* 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). */
diff --git a/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.xml b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.xml
new file mode 100644
index 000000000..4b2ccf552
--- /dev/null
+++ b/web_responsive/static/src/components/menu_odoo_searchbar/searchbar.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/menu_searchbar/searchbar.esm.js b/web_responsive/static/src/components/menu_searchbar/searchbar.esm.js
new file mode 100644
index 000000000..a976b4125
--- /dev/null
+++ b/web_responsive/static/src/components/menu_searchbar/searchbar.esm.js
@@ -0,0 +1,26 @@
+/** @odoo-module **/
+/* 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 {AppsMenuOdooSearchBar} from "@web_responsive/components/menu_odoo_searchbar/searchbar.esm";
+import {AppsMenuFuseSearchBar} from "@web_responsive/components/menu_fuse_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,
+ },
+});
diff --git a/web_responsive/static/src/components/menu_searchbar/searchbar.scss b/web_responsive/static/src/components/menu_searchbar/searchbar.scss
new file mode 100644
index 000000000..0b4c25e1a
--- /dev/null
+++ b/web_responsive/static/src/components/menu_searchbar/searchbar.scss
@@ -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;
+ }
+}
diff --git a/web_responsive/static/src/components/menu_searchbar/searchbar.xml b/web_responsive/static/src/components/menu_searchbar/searchbar.xml
new file mode 100644
index 000000000..76214f221
--- /dev/null
+++ b/web_responsive/static/src/components/menu_searchbar/searchbar.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/components/search_panel/search_panel.esm.js b/web_responsive/static/src/components/search_panel/search_panel.esm.js
deleted file mode 100644
index c6999e2b6..000000000
--- a/web_responsive/static/src/components/search_panel/search_panel.esm.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/** @odoo-module **/
-/* Copyright 2021 ITerra - Sergey Shebanin
- * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
-
-import SearchPanel from "@web/legacy/js/views/search_panel";
-import {deviceContext} from "@web_responsive/components/ui_context.esm";
-import {patch} from "web.utils";
-
-// Patch search panel to add functionality for mobile view
-patch(SearchPanel.prototype, "web_responsive.SearchPanelMobile", {
- setup() {
- this._super();
- this.state.mobileSearch = false;
- this.ui = deviceContext;
- },
- getActiveSummary() {
- const selection = [];
- for (const filter of this.model.get("sections")) {
- let filterValues = [];
- if (filter.type === "category") {
- if (filter.activeValueId) {
- const parentIds = this._getAncestorValueIds(
- filter,
- filter.activeValueId
- );
- filterValues = [...parentIds, filter.activeValueId].map(
- (valueId) => filter.values.get(valueId).display_name
- );
- }
- } else {
- let values = [];
- if (filter.groups) {
- values = [
- ...[...filter.groups.values()].map((g) => g.values),
- ].flat();
- }
- if (filter.values) {
- values = [...filter.values.values()];
- }
- filterValues = values
- .filter((v) => v.checked)
- .map((v) => v.display_name);
- }
- if (filterValues.length) {
- selection.push({
- values: filterValues,
- icon: filter.icon,
- color: filter.color,
- type: filter.type,
- });
- }
- }
- return selection;
- },
-});
diff --git a/web_responsive/static/src/components/search_panel/search_panel.scss b/web_responsive/static/src/components/search_panel/search_panel.scss
deleted file mode 100644
index eccca4547..000000000
--- a/web_responsive/static/src/components/search_panel/search_panel.scss
+++ /dev/null
@@ -1,112 +0,0 @@
-/* Copyright 2021 ITerra - Sergey Shebanin
- * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
-
-.o_web_client {
- .o_mobile_search {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- padding: 0;
- width: 100%;
- background-color: white;
- z-index: $zindex-modal;
- overflow: auto;
- .o_mobile_search_header {
- height: 46px;
- margin-bottom: 10px;
- width: 100%;
- background-color: $o-brand-odoo;
- color: white;
- span:active {
- background-color: darken($o-brand-primary, 10%);
- }
- span {
- cursor: pointer;
- }
- }
- .o_searchview_input_container {
- display: flex;
- padding: 15px 20px 0 20px;
- position: relative;
- .o_searchview_input {
- width: 100%;
- margin-bottom: 15px;
- border-bottom: 1px solid $o-brand-secondary;
- }
- .o_searchview_facet {
- border-radius: 10px;
- display: inline-flex;
- order: 1;
- .o_searchview_facet_label {
- border-radius: 2em 0em 0em 2em;
- }
- }
- .o_searchview_autocomplete {
- top: 100%;
- > li {
- margin: 5px 0px;
- }
- }
- }
- .o_mobile_search_filter {
- padding-bottom: 15%;
- .o_dropdown {
- width: 100%;
- margin: 15px 5px 0px 5px;
- border: solid 1px darken($gray-200, 20%);
- }
- .o_dropdown_toggler_btn {
- width: 100%;
- text-align: left;
-
- &:after {
- display: none;
- }
- }
-
- // We disable the backdrop in this case because it prevents any
- // interaction outside of a dropdown while it is open.
- .dropdown-backdrop {
- z-index: -1;
- }
- .dropdown-menu {
- // Here we use !important because of popper js adding custom style
- // to element so to override it use !important
- position: relative !important;
- width: 100% !important;
- transform: translate3d(0, 0, 0) !important;
- box-shadow: none;
- border: none;
- color: $gray-600;
- .divider {
- margin: 0px;
- }
- > li > a {
- padding: 10px 26px;
- }
- }
- }
- .o_mobile_search_show_result {
- padding: 10px;
- font-size: 15px;
- }
- }
-}
-// Search panel
-@include media-breakpoint-down(sm) {
- .o_controller_with_searchpanel {
- display: block;
- .o_search_panel {
- height: auto;
- padding: 8px;
- border-left: 1px solid $gray-300;
- section {
- padding: 0px 16px;
- }
- }
- .o_search_panel_summary {
- cursor: pointer;
- }
- }
-}
diff --git a/web_responsive/static/src/components/search_panel/search_panel.xml b/web_responsive/static/src/components/search_panel/search_panel.xml
deleted file mode 100644
index a30d39cfa..000000000
--- a/web_responsive/static/src/components/search_panel/search_panel.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- SEE RESULT
-
-
-
-
diff --git a/web_responsive/static/src/components/ui_context.esm.js b/web_responsive/static/src/components/ui_context.esm.js
deleted file mode 100644
index 445fbd777..000000000
--- a/web_responsive/static/src/components/ui_context.esm.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/** @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";
-import {debounce} from "@web/core/utils/timing";
-import config from "web.config";
-import core from "web.core";
-
-import Context from "web.Context";
-
-// Legacy variant
-// TODO: remove when legacy code will dropped from odoo
-// TODO: then move context definition inside service start function
-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
-const uiContextService = {
- dependencies: ["ui"],
- start(env, {ui}) {
- window.addEventListener(
- "resize",
- debounce(() => {
- const state = deviceContext;
- if (state.size !== ui.size) {
- state.size = ui.size;
- }
- if (state.isSmall !== ui.isSmall) {
- state.isSmall = ui.isSmall;
- config.device.isMobile = state.isSmall;
- config.device.size_class = state.size;
- core.bus.trigger("UI_CONTEXT:IS_SMALL_CHANGED");
- }
- }, 150) // UIService debounce for this event is 100
- );
-
- return deviceContext;
- },
-};
-
-registry.category("services").add("ui_context", uiContextService);
diff --git a/web_responsive/static/src/legacy/js/web_responsive.esm.js b/web_responsive/static/src/legacy/js/web_responsive.esm.js
new file mode 100644
index 000000000..3c5c8520c
--- /dev/null
+++ b/web_responsive/static/src/legacy/js/web_responsive.esm.js
@@ -0,0 +1,27 @@
+/** @odoo-module **/
+
+/* 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 {debounce} from "@web/core/utils/timing";
+
+// Fix for iOS Safari to set correct viewport height
+// https://github.com/Faisal-Manzer/postcss-viewport-height-correction
+export function setViewportProperty(doc) {
+ function handleResize() {
+ requestAnimationFrame(function () {
+ doc.style.setProperty("--vh100", doc.clientHeight + "px");
+ });
+ }
+
+ handleResize();
+ return handleResize;
+}
+
+window.addEventListener(
+ "resize",
+ debounce(setViewportProperty(document.documentElement), 25)
+);
diff --git a/web_responsive/static/src/legacy/js/web_responsive.js b/web_responsive/static/src/legacy/js/web_responsive.js
deleted file mode 100644
index e96dc3010..000000000
--- a/web_responsive/static/src/legacy/js/web_responsive.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/* 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). */
-odoo.define("web_responsive", function () {
- "use strict";
- // Fix for iOS Safari to set correct viewport height
- // https://github.com/Faisal-Manzer/postcss-viewport-height-correction
- function setViewportProperty(doc) {
- function handleResize() {
- requestAnimationFrame(function updateViewportHeight() {
- doc.style.setProperty("--vh100", doc.clientHeight + "px");
- });
- }
- handleResize();
- return handleResize;
- }
- window.addEventListener(
- "resize",
- _.debounce(setViewportProperty(document.documentElement), 100)
- );
-});
diff --git a/web_responsive/static/src/legacy/scss/big_boxes.scss b/web_responsive/static/src/legacy/scss/big_boxes.scss
new file mode 100644
index 000000000..180a3bdfe
--- /dev/null
+++ b/web_responsive/static/src/legacy/scss/big_boxes.scss
@@ -0,0 +1,95 @@
+/* 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). */
+
+$big-checkbox-size: 1.5em;
+
+// Big checkboxes
+.o_list_view,
+.o_setting_container .o_setting_box {
+ .o_setting_right_pane {
+ margin-left: 34px;
+ }
+
+ .o-checkbox:not(.o_boolean_toggle) {
+ margin-right: 10px;
+ margin-top: -6px;
+
+ &.d-inline-block {
+ display: block !important;
+ }
+
+ .form-check-input {
+ height: $big-checkbox-size;
+ width: $big-checkbox-size;
+ }
+ }
+
+ .o_optional_columns_dropdown {
+ .o-dropdown--menu {
+ display: flex !important;
+ flex-direction: column;
+ margin: 0;
+ }
+
+ .o-checkbox {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin: 0;
+ }
+
+ .form-check-input {
+ margin-bottom: 2px;
+ }
+ }
+}
+
+.o_add_favorite + .o_accordion_values {
+ .o_add_favorite_props {
+ display: flex;
+ flex-direction: column;
+ margin: 0;
+ }
+
+ .o_add_favorite_name {
+ margin-bottom: 0.5rem;
+ max-width: 100%;
+ }
+
+ .form-check-input {
+ height: $big-checkbox-size;
+ width: $big-checkbox-size;
+ }
+
+ .form-check-label {
+ line-height: normal;
+ }
+
+ .o-checkbox {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin: 0;
+ }
+}
+
+.o_setting_container .o_setting_box {
+ .o-checkbox:not(.o_boolean_toggle) {
+ .form-check-label {
+ &::after {
+ width: 24px;
+ height: 24px;
+ }
+
+ &::before {
+ outline: none !important;
+ border: 1px solid #4c4c4c;
+ width: 24px;
+ height: 24px;
+ }
+ }
+ }
+}
diff --git a/web_responsive/static/src/legacy/scss/form_variable.scss b/web_responsive/static/src/legacy/scss/form_variable.scss
new file mode 100644
index 000000000..442e98a87
--- /dev/null
+++ b/web_responsive/static/src/legacy/scss/form_variable.scss
@@ -0,0 +1,5 @@
+/* Copyright 2023 Taras Shabaranskyi
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+
+$o-form-renderer-max-width: 3840px;
+$o-form-view-sheet-max-width: 2560px;
diff --git a/web_responsive/static/src/legacy/scss/list_sticky_header.scss b/web_responsive/static/src/legacy/scss/list_sticky_header.scss
new file mode 100644
index 000000000..63cd160f9
--- /dev/null
+++ b/web_responsive/static/src/legacy/scss/list_sticky_header.scss
@@ -0,0 +1,26 @@
+/* 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). */
+
+.o_mobile_sticky {
+ transition: top 0.5s;
+}
+
+// Sticky Header & Footer in List View
+.o_list_view {
+ .o_list_table {
+ thead {
+ box-shadow: 0 1px 0 0 var(--ListRenderer-thead-border-end-color);
+ }
+
+ .o_list_footer {
+ position: sticky;
+ bottom: 0;
+ z-index: 2;
+ background-color: var(--ListRenderer-thead-bg-color);
+ box-shadow: 0 -1px 0 -1px var(--ListRenderer-thead-border-end-color);
+ }
+ }
+}
diff --git a/web_responsive/static/src/legacy/scss/primary_variable.scss b/web_responsive/static/src/legacy/scss/primary_variable.scss
new file mode 100644
index 000000000..bb1f12852
--- /dev/null
+++ b/web_responsive/static/src/legacy/scss/primary_variable.scss
@@ -0,0 +1,12 @@
+/* Copyright 2023 Taras Shabaranskyi
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+
+$app-menu-text-color: #374151 !default;
+$app-menu-background-color: rgb(233, 230, 249) !default;
+$app-menu-item-highlight: rgb(243, 240, 259) !default;
+$app-menu-box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16),
+ 0 2px 2px rgba(0, 0, 0, 0.016), 0 4px 4px rgba(0, 0, 0, 0.016),
+ 0 8px 8px rgba(0, 0, 0, 0.016), 0 16px 16px rgba(0, 0, 0, 0.016) !default;
+$app-menu-box-shadow-highlight: inset 0 0 0 1px rgba(0, 0, 0, 0.26),
+ 0 2px 2px rgba(0, 0, 0, 0.026), 0 4px 4px rgba(0, 0, 0, 0.026),
+ 0 8px 8px rgba(0, 0, 0, 0.026), 0 16px 16px rgba(0, 0, 0, 0.026) !default;
diff --git a/web_responsive/static/src/legacy/scss/web_responsive.scss b/web_responsive/static/src/legacy/scss/web_responsive.scss
index 38da47f3a..092faba20 100644
--- a/web_responsive/static/src/legacy/scss/web_responsive.scss
+++ b/web_responsive/static/src/legacy/scss/web_responsive.scss
@@ -1,89 +1,42 @@
/* 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). */
$chatter_zone_width: 35% !important;
-// Support for long titles
-@include media-breakpoint-up(md) {
- .o_form_view .oe_button_box + .oe_title,
- .o_form_view .oe_button_box + .oe_avatar + .oe_title {
- /* Button-box has a hardcoded width of 132px per button and have three columns */
- width: calc(100% - 450px);
- }
-}
+// Allow sticky header
+.o_action_manager {
+ .o_form_view {
+ overflow: unset;
-// Scroll all but top bar
-html .o_web_client .o_action_manager .o_action {
- @include media-breakpoint-down(sm) {
- overflow: auto;
-
- .o_content {
- overflow: visible !important;
+ .o_form_view_container {
+ overflow: auto;
}
}
- max-width: 100%;
}
@include media-breakpoint-down(sm) {
- .ui-menu .ui-menu-item {
+ .ui-menu-item-wrapper {
+ display: inline-flex !important;
+ align-items: center;
height: 35px;
- font-size: 15px;
}
- .o_calendar_view .o_calendar_widget {
- .fc-timeGridDay-view .fc-axis,
- .fc-timeGridWeek-view .fc-axis {
- padding-left: 0px;
- }
+ .o_calendar_widget {
.fc-dayGridMonth-view {
- padding-left: 0px;
.fc-week-number {
display: none;
}
}
- .fc-dayGridYear-view {
- padding-left: 0px;
- > .fc-month-container {
- width: 95%;
- }
- }
+
.fc-timeGridDay-view {
- .fc-week-number {
- padding: 0 4px;
- width: 1em;
- white-space: normal;
- text-align: center;
- }
.fc-day-header {
vertical-align: middle;
}
}
- .fc-timeGridWeek-view .fc-widget-header {
- word-spacing: 4em;
- white-space: normal;
- text-align: center;
- }
}
- .o_base_settings .o_setting_container {
- display: block;
- .settings_tab {
- flex-flow: row nowrap;
- padding-top: 0px;
- .tab {
- padding-right: 16px;
- }
- .selected {
- background-color: #212529;
- box-shadow: inset 0 -5px #7c7bad;
- }
- }
- .settings > .app_settings_block .o_settings_container {
- padding-left: 0;
- padding-right: 0;
- }
- }
- .o_kanban_view .o_control_panel .o_cp_bottom_right .o_cp_pager .btn-group {
+ .o_kanban_view .o_cp_pager .btn-group {
top: -1px;
}
.o_kanban_renderer {
@@ -98,42 +51,35 @@ html .o_web_client .o_action_manager .o_action {
// Form views
.o_form_editable {
- .o_form_sheet {
- max-width: calc(100% - 32px) !important;
- overflow-x: auto;
- }
-
.o_cell .o_form_label:not(.o_status):not(.o_calendar_invitation) {
min-height: 23px;
@include media-breakpoint-up(md) {
margin-bottom: 10px;
}
}
+
.o_horizontal_separator {
font-size: 14px;
}
// Some UX improvements for form in edit mode
@include media-breakpoint-down(sm) {
- .nav-item {
- border-bottom: 1px solid #dee2e6;
- }
- .nav-tabs {
- border-bottom: none;
- }
&.o_form_editable .o_field_widget {
&:not(.o_stat_info):not(.o_readonly_modifier):not(.oe_form_field_html):not(.o_field_image) {
min-height: 35px;
}
+
.o_x2m_control_panel {
- margin-bottom: 10px;
+ margin-bottom: 10px !important;
}
+
&.o_field_float_percentage,
&.o_field_monetary,
&.o_field_many2many_selection,
.o_field_many2one_selection {
align-items: center;
}
+
.o_field_many2one_selection .o_input_dropdown,
&.o_datepicker,
&.o_partner_autocomplete_info {
@@ -141,43 +87,17 @@ html .o_web_client .o_action_manager .o_action {
min-height: 35px;
}
}
+
.o_external_button {
- margin-left: 10px;
+ margin-left: 5px;
}
+
.o_dropdown_button,
.o_datepicker_button {
- top: 8px;
+ top: 50%;
right: 6px;
bottom: auto;
- }
- .o_field_many2many_selection .o_dropdown_button {
- top: 0 !important;
- }
- }
- }
-
- // Sticky statusbar
-
- .o_form_statusbar {
- position: sticky !important;
- top: 0;
- z-index: 2;
- }
-
- // Support for long title (with ellipsis)
- .oe_title {
- .o_field_widget:not(.oe_inline) {
- display: block;
- span:not(.o_field_translate) {
- display: block;
- max-width: 100%;
- text-overflow: ellipsis;
- white-space: nowrap;
- overflow: hidden;
- width: initial;
- &:active {
- white-space: normal;
- }
+ transform: translateY(-50%);
}
}
}
@@ -185,20 +105,6 @@ html .o_web_client .o_action_manager .o_action {
@include media-breakpoint-down(sm) {
min-width: auto;
- // More buttons border
- .oe_button_box {
- .o_dropdown_more {
- button:last-child {
- border-right: 1px solid $gray-400 !important;
- }
- }
- }
-
- .oe_button_box + .oe_title,
- .oe_button_box + .oe_avatar + .oe_title {
- width: 100%;
- }
-
// Avoid overflow on modals
.o_form_sheet {
min-width: auto;
@@ -209,43 +115,13 @@ html .o_web_client .o_action_manager .o_action {
// Overrides another !important
width: auto !important;
}
-
- // Full width in form sheets
- .o_form_sheet,
- .o_FormRenderer_chatterContainer {
- min-width: auto;
- max-width: 98% !important;
- }
-
- // Settings pages
- .app_settings_block {
- .row {
- margin: 0;
- }
- }
-
- .o_FormRenderer_chatterContainer {
- padding-top: initial;
-
- // Display send button on small screens
- .o_Chatter_composer {
- &.o-has-current-partner-avatar {
- grid-template-columns: 0px 1fr;
- padding: 1rem 1rem 1.5rem 1rem !important;
- }
-
- .o_Composer_sidebarMain {
- display: none;
- }
- }
- }
}
}
//No content message improvements on mobile
@include media-breakpoint-down(md) {
.o_view_nocontent {
- top: 80px;
+ top: 53px;
}
.o_nocontent_help {
box-shadow: none;
@@ -256,112 +132,16 @@ html .o_web_client .o_action_manager .o_action {
}
}
-// Sticky Header & Footer in List View
-.o_list_view {
- .table-responsive {
- overflow: visible;
- .o_list_table {
- // th & td are here for compatibility with chrome
- thead tr:nth-child(1) th {
- position: sticky !important;
- top: 0;
- z-index: 999;
- }
- thead tr:nth-child(1) th {
- background-color: var(--ListRenderer-thead-bg-color);
- border-top: none !important;
- border-bottom: none !important;
- border-left: transparent;
- box-shadow: inset 0 0 0 $o-community-color,
- inset 0 -1px 0 $o-community-color;
- }
- tfoot,
- tfoot tr:nth-child(1) td {
- position: sticky;
- bottom: 0;
- }
- tfoot tr:nth-child(1) td {
- background-color: $o-list-footer-bg-color;
- border-top: none !important;
- border-bottom: none !important;
- box-shadow: inset 0 1px 0 $o-community-color,
- inset 0 0 0 $o-community-color;
- }
- }
- .table {
- thead tr:nth-child(1) th {
- z-index: 1;
- }
- }
- }
-}
-
-// Big checkboxes
-.o_list_view,
-.o_setting_container .o_setting_box {
- .o_setting_right_pane {
- margin-left: 34px;
- }
- .o-checkbox:not(.o_boolean_toggle) {
- margin-right: 10px;
- margin-top: -6px;
- &.d-inline-block {
- display: block !important;
- }
- .form-check-input {
- height: 24px;
- width: 24px;
- }
- }
- .o_optional_columns_dropdown,
- .o_add_favorite {
- .o-checkbox {
- margin-top: 0;
- }
- .form-check-input {
- height: 1em !important;
- width: 1em !important;
- }
- }
-}
-
-.o_setting_container .o_setting_box {
- .o_setting_right_pane {
- margin-left: 34px;
- }
- .o-checkbox:not(.o_boolean_toggle) {
- margin-right: 10px;
- .form-check-label {
- &::after {
- width: 24px;
- height: 24px;
- }
- &::before {
- outline: none !important;
- border: 1px solid #4c4c4c;
- width: 24px;
- height: 24px;
- }
- }
- }
-}
-
-.o_chatter_header_container {
- padding-top: $grid-gutter-width * 0.5;
- top: 0;
- position: sticky;
- background-color: $o-view-background-color;
- z-index: 1;
-}
-
-.o_FormRenderer_chatterContainer {
+.o-mail-Form-chatter {
&.o-isInFormSheetBg:not(.o-aside) {
background-color: $white;
+
&:not(.o-aside) {
width: auto;
border-top: 1px solid $border-color;
}
}
+
&.o-aside {
flex: 0 0 $chatter_zone_width;
max-width: initial;
@@ -380,20 +160,56 @@ body:not(.o_statusbar_buttons) {
margin-bottom: 0 !important;
}
-.w-26 {
- width: 26%;
-}
-
-// Color clue to tell the difference between a note and a public message
-.o_Chatter_composer {
- // HACK: has() pseudo class is broadly supported in desktop, even FF will deploy
- // full support soon (now it's available behind a config flag)
- // https://caniuse.com/css-has
- &:has(div.o_Composer_coreHeader) {
- background-color: lighten($o-brand-primary, 40%);
- }
-}
-
.o_searchview_autocomplete {
z-index: 999;
}
+
+// Color clue to tell the difference between a note and a public message
+// HACK: has() pseudo class is broadly supported in desktop, even FF will deploy
+// full support soon (now it's available behind a config flag)
+// https://caniuse.com/css-has
+.o-mail-Chatter-top:has(.o-mail-Chatter-sendMessage.active) {
+ .o-mail-Composer {
+ background-color: lighten($o-brand-primary, 35%);
+ padding-top: 0.25rem !important;
+ }
+
+ @include media-breakpoint-up(sm) {
+ .o-mail-Composer {
+ padding-top: 0.5rem !important;
+ }
+ }
+}
+
+@include media-breakpoint-up(md) {
+ .app_settings_block > h2,
+ .app_settings_block > div > h2 {
+ @include o-position-sticky(0);
+ z-index: 10;
+ }
+}
+
+.o_list_table {
+ .o_handle_cell,
+ .o_list_record_remove {
+ vertical-align: middle;
+ }
+}
+
+.o_action_manager {
+ .dropdown-menu {
+ max-height: 70vh;
+ max-height: 70dvh;
+ }
+
+ .o_searchview_input {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+
+ .o_control_panel_main {
+ .btn {
+ white-space: nowrap;
+ }
+ }
+}
diff --git a/web_responsive/static/src/legacy/xml/custom_favorite_item.xml b/web_responsive/static/src/legacy/xml/custom_favorite_item.xml
new file mode 100644
index 000000000..be79500c6
--- /dev/null
+++ b/web_responsive/static/src/legacy/xml/custom_favorite_item.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_responsive/static/src/legacy/xml/form_buttons.xml b/web_responsive/static/src/legacy/xml/form_buttons.xml
index a8cd9a372..c8ca382ff 100644
--- a/web_responsive/static/src/legacy/xml/form_buttons.xml
+++ b/web_responsive/static/src/legacy/xml/form_buttons.xml
@@ -5,37 +5,121 @@
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.html).
-->
-
+
-
+
- New
+ New
-
+
- Save
+ Save
-
-
- Discard
+
+
+ Discard
-
+
d
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
-
+
d
-
+
@@ -107,16 +158,12 @@
-
+
-
+
+
+
+
-
+
diff --git a/web_responsive/static/src/lib/fuse/LICENSE b/web_responsive/static/src/lib/fuse/LICENSE
new file mode 100644
index 000000000..453d70be2
--- /dev/null
+++ b/web_responsive/static/src/lib/fuse/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 Kirollos Risk
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/web_responsive/static/src/lib/fuse/fuse.basic.js b/web_responsive/static/src/lib/fuse/fuse.basic.js
new file mode 100644
index 000000000..0e7292e04
--- /dev/null
+++ b/web_responsive/static/src/lib/fuse/fuse.basic.js
@@ -0,0 +1,1335 @@
+/**
+ * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io)
+ *
+ * Copyright (c) 2023 Kiro Risk (http://kiro.me)
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Fuse = factory());
+})(this, (function () { 'use strict';
+
+ function ownKeys(object, enumerableOnly) {
+ var keys = Object.keys(object);
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(object);
+ enumerableOnly && (symbols = symbols.filter(function (sym) {
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
+ })), keys.push.apply(keys, symbols);
+ }
+ return keys;
+ }
+ function _objectSpread2(target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = null != arguments[i] ? arguments[i] : {};
+ i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
+ _defineProperty(target, key, source[key]);
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
+ });
+ }
+ return target;
+ }
+ function _typeof(obj) {
+ "@babel/helpers - typeof";
+
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
+ return typeof obj;
+ } : function (obj) {
+ return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ }, _typeof(obj);
+ }
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
+ }
+ }
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ Object.defineProperty(Constructor, "prototype", {
+ writable: false
+ });
+ return Constructor;
+ }
+ function _defineProperty(obj, key, value) {
+ key = _toPropertyKey(key);
+ if (key in obj) {
+ Object.defineProperty(obj, key, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ } else {
+ obj[key] = value;
+ }
+ return obj;
+ }
+ function _toConsumableArray(arr) {
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
+ }
+ function _arrayWithoutHoles(arr) {
+ if (Array.isArray(arr)) return _arrayLikeToArray(arr);
+ }
+ function _iterableToArray(iter) {
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
+ }
+ function _unsupportedIterableToArray(o, minLen) {
+ if (!o) return;
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+ var n = Object.prototype.toString.call(o).slice(8, -1);
+ if (n === "Object" && o.constructor) n = o.constructor.name;
+ if (n === "Map" || n === "Set") return Array.from(o);
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+ }
+ function _arrayLikeToArray(arr, len) {
+ if (len == null || len > arr.length) len = arr.length;
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+ return arr2;
+ }
+ function _nonIterableSpread() {
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+ }
+ function _toPrimitive(input, hint) {
+ if (typeof input !== "object" || input === null) return input;
+ var prim = input[Symbol.toPrimitive];
+ if (prim !== undefined) {
+ var res = prim.call(input, hint || "default");
+ if (typeof res !== "object") return res;
+ throw new TypeError("@@toPrimitive must return a primitive value.");
+ }
+ return (hint === "string" ? String : Number)(input);
+ }
+ function _toPropertyKey(arg) {
+ var key = _toPrimitive(arg, "string");
+ return typeof key === "symbol" ? key : String(key);
+ }
+
+ function isArray(value) {
+ return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value);
+ }
+
+ // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/baseToString.js
+ var INFINITY = 1 / 0;
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
+ }
+ function toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+ function isString(value) {
+ return typeof value === 'string';
+ }
+ function isNumber(value) {
+ return typeof value === 'number';
+ }
+
+ // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js
+ function isBoolean(value) {
+ return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]';
+ }
+ function isObject(value) {
+ return _typeof(value) === 'object';
+ }
+
+ // Checks if `value` is object-like.
+ function isObjectLike(value) {
+ return isObject(value) && value !== null;
+ }
+ function isDefined(value) {
+ return value !== undefined && value !== null;
+ }
+ function isBlank(value) {
+ return !value.trim().length;
+ }
+
+ // Gets the `toStringTag` of `value`.
+ // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js
+ function getTag(value) {
+ return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value);
+ }
+
+ var EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available';
+ var LOGICAL_SEARCH_UNAVAILABLE = 'Logical search is not available';
+ var INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
+ var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = function LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key) {
+ return "Invalid value for key ".concat(key);
+ };
+ var PATTERN_LENGTH_TOO_LARGE = function PATTERN_LENGTH_TOO_LARGE(max) {
+ return "Pattern length exceeds max of ".concat(max, ".");
+ };
+ var MISSING_KEY_PROPERTY = function MISSING_KEY_PROPERTY(name) {
+ return "Missing ".concat(name, " property in key");
+ };
+ var INVALID_KEY_WEIGHT_VALUE = function INVALID_KEY_WEIGHT_VALUE(key) {
+ return "Property 'weight' in key '".concat(key, "' must be a positive integer");
+ };
+
+ var hasOwn = Object.prototype.hasOwnProperty;
+ var KeyStore = /*#__PURE__*/function () {
+ function KeyStore(keys) {
+ var _this = this;
+ _classCallCheck(this, KeyStore);
+ this._keys = [];
+ this._keyMap = {};
+ var totalWeight = 0;
+ keys.forEach(function (key) {
+ var obj = createKey(key);
+ _this._keys.push(obj);
+ _this._keyMap[obj.id] = obj;
+ totalWeight += obj.weight;
+ });
+
+ // Normalize weights so that their sum is equal to 1
+ this._keys.forEach(function (key) {
+ key.weight /= totalWeight;
+ });
+ }
+ _createClass(KeyStore, [{
+ key: "get",
+ value: function get(keyId) {
+ return this._keyMap[keyId];
+ }
+ }, {
+ key: "keys",
+ value: function keys() {
+ return this._keys;
+ }
+ }, {
+ key: "toJSON",
+ value: function toJSON() {
+ return JSON.stringify(this._keys);
+ }
+ }]);
+ return KeyStore;
+ }();
+ function createKey(key) {
+ var path = null;
+ var id = null;
+ var src = null;
+ var weight = 1;
+ var getFn = null;
+ if (isString(key) || isArray(key)) {
+ src = key;
+ path = createKeyPath(key);
+ id = createKeyId(key);
+ } else {
+ if (!hasOwn.call(key, 'name')) {
+ throw new Error(MISSING_KEY_PROPERTY('name'));
+ }
+ var name = key.name;
+ src = name;
+ if (hasOwn.call(key, 'weight')) {
+ weight = key.weight;
+ if (weight <= 0) {
+ throw new Error(INVALID_KEY_WEIGHT_VALUE(name));
+ }
+ }
+ path = createKeyPath(name);
+ id = createKeyId(name);
+ getFn = key.getFn;
+ }
+ return {
+ path: path,
+ id: id,
+ weight: weight,
+ src: src,
+ getFn: getFn
+ };
+ }
+ function createKeyPath(key) {
+ return isArray(key) ? key : key.split('.');
+ }
+ function createKeyId(key) {
+ return isArray(key) ? key.join('.') : key;
+ }
+
+ function get(obj, path) {
+ var list = [];
+ var arr = false;
+ var deepGet = function deepGet(obj, path, index) {
+ if (!isDefined(obj)) {
+ return;
+ }
+ if (!path[index]) {
+ // If there's no path left, we've arrived at the object we care about.
+ list.push(obj);
+ } else {
+ var key = path[index];
+ var value = obj[key];
+ if (!isDefined(value)) {
+ return;
+ }
+
+ // If we're at the last value in the path, and if it's a string/number/bool,
+ // add it to the list
+ if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) {
+ list.push(toString(value));
+ } else if (isArray(value)) {
+ arr = true;
+ // Search each item in the array.
+ for (var i = 0, len = value.length; i < len; i += 1) {
+ deepGet(value[i], path, index + 1);
+ }
+ } else if (path.length) {
+ // An object. Recurse further.
+ deepGet(value, path, index + 1);
+ }
+ }
+ };
+
+ // Backwards compatibility (since path used to be a string)
+ deepGet(obj, isString(path) ? path.split('.') : path, 0);
+ return arr ? list : list[0];
+ }
+
+ var MatchOptions = {
+ // Whether the matches should be included in the result set. When `true`, each record in the result
+ // set will include the indices of the matched characters.
+ // These can consequently be used for highlighting purposes.
+ includeMatches: false,
+ // When `true`, the matching function will continue to the end of a search pattern even if
+ // a perfect match has already been located in the string.
+ findAllMatches: false,
+ // Minimum number of characters that must be matched before a result is considered a match
+ minMatchCharLength: 1
+ };
+ var BasicOptions = {
+ // When `true`, the algorithm continues searching to the end of the input even if a perfect
+ // match is found before the end of the same input.
+ isCaseSensitive: false,
+ // When true, the matching function will continue to the end of a search pattern even if
+ includeScore: false,
+ // List of properties that will be searched. This also supports nested properties.
+ keys: [],
+ // Whether to sort the result list, by score
+ shouldSort: true,
+ // Default sort function: sort by ascending score, ascending index
+ sortFn: function sortFn(a, b) {
+ return a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1;
+ }
+ };
+ var FuzzyOptions = {
+ // Approximately where in the text is the pattern expected to be found?
+ location: 0,
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
+ // (of both letters and location), a threshold of '1.0' would match anything.
+ threshold: 0.6,
+ // Determines how close the match must be to the fuzzy location (specified above).
+ // An exact letter match which is 'distance' characters away from the fuzzy location
+ // would score as a complete mismatch. A distance of '0' requires the match be at
+ // the exact location specified, a threshold of '1000' would require a perfect match
+ // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
+ distance: 100
+ };
+ var AdvancedOptions = {
+ // When `true`, it enables the use of unix-like search commands
+ useExtendedSearch: false,
+ // The get function to use when fetching an object's properties.
+ // The default will search nested paths *ie foo.bar.baz*
+ getFn: get,
+ // When `true`, search will ignore `location` and `distance`, so it won't matter
+ // where in the string the pattern appears.
+ // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
+ ignoreLocation: false,
+ // When `true`, the calculation for the relevance score (used for sorting) will
+ // ignore the field-length norm.
+ // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
+ ignoreFieldNorm: false,
+ // The weight to determine how much field length norm effects scoring.
+ fieldNormWeight: 1
+ };
+ var Config = _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({}, BasicOptions), MatchOptions), FuzzyOptions), AdvancedOptions);
+
+ var SPACE = /[^ ]+/g;
+
+ // Field-length norm: the shorter the field, the higher the weight.
+ // Set to 3 decimals to reduce index size.
+ function norm() {
+ var weight = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
+ var mantissa = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
+ var cache = new Map();
+ var m = Math.pow(10, mantissa);
+ return {
+ get: function get(value) {
+ var numTokens = value.match(SPACE).length;
+ if (cache.has(numTokens)) {
+ return cache.get(numTokens);
+ }
+
+ // Default function is 1/sqrt(x), weight makes that variable
+ var norm = 1 / Math.pow(numTokens, 0.5 * weight);
+
+ // In place of `toFixed(mantissa)`, for faster computation
+ var n = parseFloat(Math.round(norm * m) / m);
+ cache.set(numTokens, n);
+ return n;
+ },
+ clear: function clear() {
+ cache.clear();
+ }
+ };
+ }
+
+ var FuseIndex = /*#__PURE__*/function () {
+ function FuseIndex() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
+ _ref$getFn = _ref.getFn,
+ getFn = _ref$getFn === void 0 ? Config.getFn : _ref$getFn,
+ _ref$fieldNormWeight = _ref.fieldNormWeight,
+ fieldNormWeight = _ref$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref$fieldNormWeight;
+ _classCallCheck(this, FuseIndex);
+ this.norm = norm(fieldNormWeight, 3);
+ this.getFn = getFn;
+ this.isCreated = false;
+ this.setIndexRecords();
+ }
+ _createClass(FuseIndex, [{
+ key: "setSources",
+ value: function setSources() {
+ var docs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.docs = docs;
+ }
+ }, {
+ key: "setIndexRecords",
+ value: function setIndexRecords() {
+ var records = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.records = records;
+ }
+ }, {
+ key: "setKeys",
+ value: function setKeys() {
+ var _this = this;
+ var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.keys = keys;
+ this._keysMap = {};
+ keys.forEach(function (key, idx) {
+ _this._keysMap[key.id] = idx;
+ });
+ }
+ }, {
+ key: "create",
+ value: function create() {
+ var _this2 = this;
+ if (this.isCreated || !this.docs.length) {
+ return;
+ }
+ this.isCreated = true;
+
+ // List is Array
+ if (isString(this.docs[0])) {
+ this.docs.forEach(function (doc, docIndex) {
+ _this2._addString(doc, docIndex);
+ });
+ } else {
+ // List is Array
+ this.docs.forEach(function (doc, docIndex) {
+ _this2._addObject(doc, docIndex);
+ });
+ }
+ this.norm.clear();
+ }
+ // Adds a doc to the end of the index
+ }, {
+ key: "add",
+ value: function add(doc) {
+ var idx = this.size();
+ if (isString(doc)) {
+ this._addString(doc, idx);
+ } else {
+ this._addObject(doc, idx);
+ }
+ }
+ // Removes the doc at the specified index of the index
+ }, {
+ key: "removeAt",
+ value: function removeAt(idx) {
+ this.records.splice(idx, 1);
+
+ // Change ref index of every subsquent doc
+ for (var i = idx, len = this.size(); i < len; i += 1) {
+ this.records[i].i -= 1;
+ }
+ }
+ }, {
+ key: "getValueForItemAtKeyId",
+ value: function getValueForItemAtKeyId(item, keyId) {
+ return item[this._keysMap[keyId]];
+ }
+ }, {
+ key: "size",
+ value: function size() {
+ return this.records.length;
+ }
+ }, {
+ key: "_addString",
+ value: function _addString(doc, docIndex) {
+ if (!isDefined(doc) || isBlank(doc)) {
+ return;
+ }
+ var record = {
+ v: doc,
+ i: docIndex,
+ n: this.norm.get(doc)
+ };
+ this.records.push(record);
+ }
+ }, {
+ key: "_addObject",
+ value: function _addObject(doc, docIndex) {
+ var _this3 = this;
+ var record = {
+ i: docIndex,
+ $: {}
+ };
+
+ // Iterate over every key (i.e, path), and fetch the value at that key
+ this.keys.forEach(function (key, keyIndex) {
+ var value = key.getFn ? key.getFn(doc) : _this3.getFn(doc, key.path);
+ if (!isDefined(value)) {
+ return;
+ }
+ if (isArray(value)) {
+ var subRecords = [];
+ var stack = [{
+ nestedArrIndex: -1,
+ value: value
+ }];
+ while (stack.length) {
+ var _stack$pop = stack.pop(),
+ nestedArrIndex = _stack$pop.nestedArrIndex,
+ _value = _stack$pop.value;
+ if (!isDefined(_value)) {
+ continue;
+ }
+ if (isString(_value) && !isBlank(_value)) {
+ var subRecord = {
+ v: _value,
+ i: nestedArrIndex,
+ n: _this3.norm.get(_value)
+ };
+ subRecords.push(subRecord);
+ } else if (isArray(_value)) {
+ _value.forEach(function (item, k) {
+ stack.push({
+ nestedArrIndex: k,
+ value: item
+ });
+ });
+ } else ;
+ }
+ record.$[keyIndex] = subRecords;
+ } else if (isString(value) && !isBlank(value)) {
+ var _subRecord = {
+ v: value,
+ n: _this3.norm.get(value)
+ };
+ record.$[keyIndex] = _subRecord;
+ }
+ });
+ this.records.push(record);
+ }
+ }, {
+ key: "toJSON",
+ value: function toJSON() {
+ return {
+ keys: this.keys,
+ records: this.records
+ };
+ }
+ }]);
+ return FuseIndex;
+ }();
+ function createIndex(keys, docs) {
+ var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
+ _ref2$getFn = _ref2.getFn,
+ getFn = _ref2$getFn === void 0 ? Config.getFn : _ref2$getFn,
+ _ref2$fieldNormWeight = _ref2.fieldNormWeight,
+ fieldNormWeight = _ref2$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref2$fieldNormWeight;
+ var myIndex = new FuseIndex({
+ getFn: getFn,
+ fieldNormWeight: fieldNormWeight
+ });
+ myIndex.setKeys(keys.map(createKey));
+ myIndex.setSources(docs);
+ myIndex.create();
+ return myIndex;
+ }
+ function parseIndex(data) {
+ var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+ _ref3$getFn = _ref3.getFn,
+ getFn = _ref3$getFn === void 0 ? Config.getFn : _ref3$getFn,
+ _ref3$fieldNormWeight = _ref3.fieldNormWeight,
+ fieldNormWeight = _ref3$fieldNormWeight === void 0 ? Config.fieldNormWeight : _ref3$fieldNormWeight;
+ var keys = data.keys,
+ records = data.records;
+ var myIndex = new FuseIndex({
+ getFn: getFn,
+ fieldNormWeight: fieldNormWeight
+ });
+ myIndex.setKeys(keys);
+ myIndex.setIndexRecords(records);
+ return myIndex;
+ }
+
+ function computeScore$1(pattern) {
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+ _ref$errors = _ref.errors,
+ errors = _ref$errors === void 0 ? 0 : _ref$errors,
+ _ref$currentLocation = _ref.currentLocation,
+ currentLocation = _ref$currentLocation === void 0 ? 0 : _ref$currentLocation,
+ _ref$expectedLocation = _ref.expectedLocation,
+ expectedLocation = _ref$expectedLocation === void 0 ? 0 : _ref$expectedLocation,
+ _ref$distance = _ref.distance,
+ distance = _ref$distance === void 0 ? Config.distance : _ref$distance,
+ _ref$ignoreLocation = _ref.ignoreLocation,
+ ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation;
+ var accuracy = errors / pattern.length;
+ if (ignoreLocation) {
+ return accuracy;
+ }
+ var proximity = Math.abs(expectedLocation - currentLocation);
+ if (!distance) {
+ // Dodge divide by zero error.
+ return proximity ? 1.0 : accuracy;
+ }
+ return accuracy + proximity / distance;
+ }
+
+ function convertMaskToIndices() {
+ var matchmask = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ var minMatchCharLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Config.minMatchCharLength;
+ var indices = [];
+ var start = -1;
+ var end = -1;
+ var i = 0;
+ for (var len = matchmask.length; i < len; i += 1) {
+ var match = matchmask[i];
+ if (match && start === -1) {
+ start = i;
+ } else if (!match && start !== -1) {
+ end = i - 1;
+ if (end - start + 1 >= minMatchCharLength) {
+ indices.push([start, end]);
+ }
+ start = -1;
+ }
+ }
+
+ // (i-1 - start) + 1 => i - start
+ if (matchmask[i - 1] && i - start >= minMatchCharLength) {
+ indices.push([start, i - 1]);
+ }
+ return indices;
+ }
+
+ // Machine word size
+ var MAX_BITS = 32;
+
+ function search(text, pattern, patternAlphabet) {
+ var _ref = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {},
+ _ref$location = _ref.location,
+ location = _ref$location === void 0 ? Config.location : _ref$location,
+ _ref$distance = _ref.distance,
+ distance = _ref$distance === void 0 ? Config.distance : _ref$distance,
+ _ref$threshold = _ref.threshold,
+ threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold,
+ _ref$findAllMatches = _ref.findAllMatches,
+ findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches,
+ _ref$minMatchCharLeng = _ref.minMatchCharLength,
+ minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng,
+ _ref$includeMatches = _ref.includeMatches,
+ includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches,
+ _ref$ignoreLocation = _ref.ignoreLocation,
+ ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation;
+ if (pattern.length > MAX_BITS) {
+ throw new Error(PATTERN_LENGTH_TOO_LARGE(MAX_BITS));
+ }
+ var patternLen = pattern.length;
+ // Set starting location at beginning text and initialize the alphabet.
+ var textLen = text.length;
+ // Handle the case when location > text.length
+ var expectedLocation = Math.max(0, Math.min(location, textLen));
+ // Highest score beyond which we give up.
+ var currentThreshold = threshold;
+ // Is there a nearby exact match? (speedup)
+ var bestLocation = expectedLocation;
+
+ // Performance: only computer matches when the minMatchCharLength > 1
+ // OR if `includeMatches` is true.
+ var computeMatches = minMatchCharLength > 1 || includeMatches;
+ // A mask of the matches, used for building the indices
+ var matchMask = computeMatches ? Array(textLen) : [];
+ var index;
+
+ // Get all exact matches, here for speed up
+ while ((index = text.indexOf(pattern, bestLocation)) > -1) {
+ var score = computeScore$1(pattern, {
+ currentLocation: index,
+ expectedLocation: expectedLocation,
+ distance: distance,
+ ignoreLocation: ignoreLocation
+ });
+ currentThreshold = Math.min(score, currentThreshold);
+ bestLocation = index + patternLen;
+ if (computeMatches) {
+ var i = 0;
+ while (i < patternLen) {
+ matchMask[index + i] = 1;
+ i += 1;
+ }
+ }
+ }
+
+ // Reset the best location
+ bestLocation = -1;
+ var lastBitArr = [];
+ var finalScore = 1;
+ var binMax = patternLen + textLen;
+ var mask = 1 << patternLen - 1;
+ for (var _i = 0; _i < patternLen; _i += 1) {
+ // Scan for the best match; each iteration allows for one more error.
+ // Run a binary search to determine how far from the match location we can stray
+ // at this error level.
+ var binMin = 0;
+ var binMid = binMax;
+ while (binMin < binMid) {
+ var _score = computeScore$1(pattern, {
+ errors: _i,
+ currentLocation: expectedLocation + binMid,
+ expectedLocation: expectedLocation,
+ distance: distance,
+ ignoreLocation: ignoreLocation
+ });
+ if (_score <= currentThreshold) {
+ binMin = binMid;
+ } else {
+ binMax = binMid;
+ }
+ binMid = Math.floor((binMax - binMin) / 2 + binMin);
+ }
+
+ // Use the result from this iteration as the maximum for the next.
+ binMax = binMid;
+ var start = Math.max(1, expectedLocation - binMid + 1);
+ var finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen;
+
+ // Initialize the bit array
+ var bitArr = Array(finish + 2);
+ bitArr[finish + 1] = (1 << _i) - 1;
+ for (var j = finish; j >= start; j -= 1) {
+ var currentLocation = j - 1;
+ var charMatch = patternAlphabet[text.charAt(currentLocation)];
+ if (computeMatches) {
+ // Speed up: quick bool to int conversion (i.e, `charMatch ? 1 : 0`)
+ matchMask[currentLocation] = +!!charMatch;
+ }
+
+ // First pass: exact match
+ bitArr[j] = (bitArr[j + 1] << 1 | 1) & charMatch;
+
+ // Subsequent passes: fuzzy match
+ if (_i) {
+ bitArr[j] |= (lastBitArr[j + 1] | lastBitArr[j]) << 1 | 1 | lastBitArr[j + 1];
+ }
+ if (bitArr[j] & mask) {
+ finalScore = computeScore$1(pattern, {
+ errors: _i,
+ currentLocation: currentLocation,
+ expectedLocation: expectedLocation,
+ distance: distance,
+ ignoreLocation: ignoreLocation
+ });
+
+ // This match will almost certainly be better than any existing match.
+ // But check anyway.
+ if (finalScore <= currentThreshold) {
+ // Indeed it is
+ currentThreshold = finalScore;
+ bestLocation = currentLocation;
+
+ // Already passed `loc`, downhill from here on in.
+ if (bestLocation <= expectedLocation) {
+ break;
+ }
+
+ // When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
+ start = Math.max(1, 2 * expectedLocation - bestLocation);
+ }
+ }
+ }
+
+ // No hope for a (better) match at greater error levels.
+ var _score2 = computeScore$1(pattern, {
+ errors: _i + 1,
+ currentLocation: expectedLocation,
+ expectedLocation: expectedLocation,
+ distance: distance,
+ ignoreLocation: ignoreLocation
+ });
+ if (_score2 > currentThreshold) {
+ break;
+ }
+ lastBitArr = bitArr;
+ }
+ var result = {
+ isMatch: bestLocation >= 0,
+ // Count exact matches (those with a score of 0) to be "almost" exact
+ score: Math.max(0.001, finalScore)
+ };
+ if (computeMatches) {
+ var indices = convertMaskToIndices(matchMask, minMatchCharLength);
+ if (!indices.length) {
+ result.isMatch = false;
+ } else if (includeMatches) {
+ result.indices = indices;
+ }
+ }
+ return result;
+ }
+
+ function createPatternAlphabet(pattern) {
+ var mask = {};
+ for (var i = 0, len = pattern.length; i < len; i += 1) {
+ var _char = pattern.charAt(i);
+ mask[_char] = (mask[_char] || 0) | 1 << len - i - 1;
+ }
+ return mask;
+ }
+
+ var BitapSearch = /*#__PURE__*/function () {
+ function BitapSearch(pattern) {
+ var _this = this;
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+ _ref$location = _ref.location,
+ location = _ref$location === void 0 ? Config.location : _ref$location,
+ _ref$threshold = _ref.threshold,
+ threshold = _ref$threshold === void 0 ? Config.threshold : _ref$threshold,
+ _ref$distance = _ref.distance,
+ distance = _ref$distance === void 0 ? Config.distance : _ref$distance,
+ _ref$includeMatches = _ref.includeMatches,
+ includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches,
+ _ref$findAllMatches = _ref.findAllMatches,
+ findAllMatches = _ref$findAllMatches === void 0 ? Config.findAllMatches : _ref$findAllMatches,
+ _ref$minMatchCharLeng = _ref.minMatchCharLength,
+ minMatchCharLength = _ref$minMatchCharLeng === void 0 ? Config.minMatchCharLength : _ref$minMatchCharLeng,
+ _ref$isCaseSensitive = _ref.isCaseSensitive,
+ isCaseSensitive = _ref$isCaseSensitive === void 0 ? Config.isCaseSensitive : _ref$isCaseSensitive,
+ _ref$ignoreLocation = _ref.ignoreLocation,
+ ignoreLocation = _ref$ignoreLocation === void 0 ? Config.ignoreLocation : _ref$ignoreLocation;
+ _classCallCheck(this, BitapSearch);
+ this.options = {
+ location: location,
+ threshold: threshold,
+ distance: distance,
+ includeMatches: includeMatches,
+ findAllMatches: findAllMatches,
+ minMatchCharLength: minMatchCharLength,
+ isCaseSensitive: isCaseSensitive,
+ ignoreLocation: ignoreLocation
+ };
+ this.pattern = isCaseSensitive ? pattern : pattern.toLowerCase();
+ this.chunks = [];
+ if (!this.pattern.length) {
+ return;
+ }
+ var addChunk = function addChunk(pattern, startIndex) {
+ _this.chunks.push({
+ pattern: pattern,
+ alphabet: createPatternAlphabet(pattern),
+ startIndex: startIndex
+ });
+ };
+ var len = this.pattern.length;
+ if (len > MAX_BITS) {
+ var i = 0;
+ var remainder = len % MAX_BITS;
+ var end = len - remainder;
+ while (i < end) {
+ addChunk(this.pattern.substr(i, MAX_BITS), i);
+ i += MAX_BITS;
+ }
+ if (remainder) {
+ var startIndex = len - MAX_BITS;
+ addChunk(this.pattern.substr(startIndex), startIndex);
+ }
+ } else {
+ addChunk(this.pattern, 0);
+ }
+ }
+ _createClass(BitapSearch, [{
+ key: "searchIn",
+ value: function searchIn(text) {
+ var _this$options = this.options,
+ isCaseSensitive = _this$options.isCaseSensitive,
+ includeMatches = _this$options.includeMatches;
+ if (!isCaseSensitive) {
+ text = text.toLowerCase();
+ }
+
+ // Exact match
+ if (this.pattern === text) {
+ var _result = {
+ isMatch: true,
+ score: 0
+ };
+ if (includeMatches) {
+ _result.indices = [[0, text.length - 1]];
+ }
+ return _result;
+ }
+
+ // Otherwise, use Bitap algorithm
+ var _this$options2 = this.options,
+ location = _this$options2.location,
+ distance = _this$options2.distance,
+ threshold = _this$options2.threshold,
+ findAllMatches = _this$options2.findAllMatches,
+ minMatchCharLength = _this$options2.minMatchCharLength,
+ ignoreLocation = _this$options2.ignoreLocation;
+ var allIndices = [];
+ var totalScore = 0;
+ var hasMatches = false;
+ this.chunks.forEach(function (_ref2) {
+ var pattern = _ref2.pattern,
+ alphabet = _ref2.alphabet,
+ startIndex = _ref2.startIndex;
+ var _search = search(text, pattern, alphabet, {
+ location: location + startIndex,
+ distance: distance,
+ threshold: threshold,
+ findAllMatches: findAllMatches,
+ minMatchCharLength: minMatchCharLength,
+ includeMatches: includeMatches,
+ ignoreLocation: ignoreLocation
+ }),
+ isMatch = _search.isMatch,
+ score = _search.score,
+ indices = _search.indices;
+ if (isMatch) {
+ hasMatches = true;
+ }
+ totalScore += score;
+ if (isMatch && indices) {
+ allIndices = [].concat(_toConsumableArray(allIndices), _toConsumableArray(indices));
+ }
+ });
+ var result = {
+ isMatch: hasMatches,
+ score: hasMatches ? totalScore / this.chunks.length : 1
+ };
+ if (hasMatches && includeMatches) {
+ result.indices = allIndices;
+ }
+ return result;
+ }
+ }]);
+ return BitapSearch;
+ }();
+
+ var registeredSearchers = [];
+ function createSearcher(pattern, options) {
+ for (var i = 0, len = registeredSearchers.length; i < len; i += 1) {
+ var searcherClass = registeredSearchers[i];
+ if (searcherClass.condition(pattern, options)) {
+ return new searcherClass(pattern, options);
+ }
+ }
+ return new BitapSearch(pattern, options);
+ }
+
+ var LogicalOperator = {
+ AND: '$and',
+ OR: '$or'
+ };
+ var KeyType = {
+ PATH: '$path',
+ PATTERN: '$val'
+ };
+ var isExpression = function isExpression(query) {
+ return !!(query[LogicalOperator.AND] || query[LogicalOperator.OR]);
+ };
+ var isPath = function isPath(query) {
+ return !!query[KeyType.PATH];
+ };
+ var isLeaf = function isLeaf(query) {
+ return !isArray(query) && isObject(query) && !isExpression(query);
+ };
+ var convertToExplicit = function convertToExplicit(query) {
+ return _defineProperty({}, LogicalOperator.AND, Object.keys(query).map(function (key) {
+ return _defineProperty({}, key, query[key]);
+ }));
+ };
+
+ // When `auto` is `true`, the parse function will infer and initialize and add
+ // the appropriate `Searcher` instance
+ function parse(query, options) {
+ var _ref3 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
+ _ref3$auto = _ref3.auto,
+ auto = _ref3$auto === void 0 ? true : _ref3$auto;
+ var next = function next(query) {
+ var keys = Object.keys(query);
+ var isQueryPath = isPath(query);
+ if (!isQueryPath && keys.length > 1 && !isExpression(query)) {
+ return next(convertToExplicit(query));
+ }
+ if (isLeaf(query)) {
+ var key = isQueryPath ? query[KeyType.PATH] : keys[0];
+ var pattern = isQueryPath ? query[KeyType.PATTERN] : query[key];
+ if (!isString(pattern)) {
+ throw new Error(LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key));
+ }
+ var obj = {
+ keyId: createKeyId(key),
+ pattern: pattern
+ };
+ if (auto) {
+ obj.searcher = createSearcher(pattern, options);
+ }
+ return obj;
+ }
+ var node = {
+ children: [],
+ operator: keys[0]
+ };
+ keys.forEach(function (key) {
+ var value = query[key];
+ if (isArray(value)) {
+ value.forEach(function (item) {
+ node.children.push(next(item));
+ });
+ }
+ });
+ return node;
+ };
+ if (!isExpression(query)) {
+ query = convertToExplicit(query);
+ }
+ return next(query);
+ }
+
+ // Practical scoring function
+ function computeScore(results, _ref) {
+ var _ref$ignoreFieldNorm = _ref.ignoreFieldNorm,
+ ignoreFieldNorm = _ref$ignoreFieldNorm === void 0 ? Config.ignoreFieldNorm : _ref$ignoreFieldNorm;
+ results.forEach(function (result) {
+ var totalScore = 1;
+ result.matches.forEach(function (_ref2) {
+ var key = _ref2.key,
+ norm = _ref2.norm,
+ score = _ref2.score;
+ var weight = key ? key.weight : null;
+ totalScore *= Math.pow(score === 0 && weight ? Number.EPSILON : score, (weight || 1) * (ignoreFieldNorm ? 1 : norm));
+ });
+ result.score = totalScore;
+ });
+ }
+
+ function transformMatches(result, data) {
+ var matches = result.matches;
+ data.matches = [];
+ if (!isDefined(matches)) {
+ return;
+ }
+ matches.forEach(function (match) {
+ if (!isDefined(match.indices) || !match.indices.length) {
+ return;
+ }
+ var indices = match.indices,
+ value = match.value;
+ var obj = {
+ indices: indices,
+ value: value
+ };
+ if (match.key) {
+ obj.key = match.key.src;
+ }
+ if (match.idx > -1) {
+ obj.refIndex = match.idx;
+ }
+ data.matches.push(obj);
+ });
+ }
+
+ function transformScore(result, data) {
+ data.score = result.score;
+ }
+
+ function format(results, docs) {
+ var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
+ _ref$includeMatches = _ref.includeMatches,
+ includeMatches = _ref$includeMatches === void 0 ? Config.includeMatches : _ref$includeMatches,
+ _ref$includeScore = _ref.includeScore,
+ includeScore = _ref$includeScore === void 0 ? Config.includeScore : _ref$includeScore;
+ var transformers = [];
+ if (includeMatches) transformers.push(transformMatches);
+ if (includeScore) transformers.push(transformScore);
+ return results.map(function (result) {
+ var idx = result.idx;
+ var data = {
+ item: docs[idx],
+ refIndex: idx
+ };
+ if (transformers.length) {
+ transformers.forEach(function (transformer) {
+ transformer(result, data);
+ });
+ }
+ return data;
+ });
+ }
+
+ var Fuse$1 = /*#__PURE__*/function () {
+ function Fuse(docs) {
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ var index = arguments.length > 2 ? arguments[2] : undefined;
+ _classCallCheck(this, Fuse);
+ this.options = _objectSpread2(_objectSpread2({}, Config), options);
+ if (this.options.useExtendedSearch && !false) {
+ throw new Error(EXTENDED_SEARCH_UNAVAILABLE);
+ }
+ this._keyStore = new KeyStore(this.options.keys);
+ this.setCollection(docs, index);
+ }
+ _createClass(Fuse, [{
+ key: "setCollection",
+ value: function setCollection(docs, index) {
+ this._docs = docs;
+ if (index && !(index instanceof FuseIndex)) {
+ throw new Error(INCORRECT_INDEX_TYPE);
+ }
+ this._myIndex = index || createIndex(this.options.keys, this._docs, {
+ getFn: this.options.getFn,
+ fieldNormWeight: this.options.fieldNormWeight
+ });
+ }
+ }, {
+ key: "add",
+ value: function add(doc) {
+ if (!isDefined(doc)) {
+ return;
+ }
+ this._docs.push(doc);
+ this._myIndex.add(doc);
+ }
+ }, {
+ key: "remove",
+ value: function remove() {
+ var predicate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function /* doc, idx */ () {
+ return false;
+ };
+ var results = [];
+ for (var i = 0, len = this._docs.length; i < len; i += 1) {
+ var doc = this._docs[i];
+ if (predicate(doc, i)) {
+ this.removeAt(i);
+ i -= 1;
+ len -= 1;
+ results.push(doc);
+ }
+ }
+ return results;
+ }
+ }, {
+ key: "removeAt",
+ value: function removeAt(idx) {
+ this._docs.splice(idx, 1);
+ this._myIndex.removeAt(idx);
+ }
+ }, {
+ key: "getIndex",
+ value: function getIndex() {
+ return this._myIndex;
+ }
+ }, {
+ key: "search",
+ value: function search(query) {
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+ _ref$limit = _ref.limit,
+ limit = _ref$limit === void 0 ? -1 : _ref$limit;
+ var _this$options = this.options,
+ includeMatches = _this$options.includeMatches,
+ includeScore = _this$options.includeScore,
+ shouldSort = _this$options.shouldSort,
+ sortFn = _this$options.sortFn,
+ ignoreFieldNorm = _this$options.ignoreFieldNorm;
+ var results = isString(query) ? isString(this._docs[0]) ? this._searchStringList(query) : this._searchObjectList(query) : this._searchLogical(query);
+ computeScore(results, {
+ ignoreFieldNorm: ignoreFieldNorm
+ });
+ if (shouldSort) {
+ results.sort(sortFn);
+ }
+ if (isNumber(limit) && limit > -1) {
+ results = results.slice(0, limit);
+ }
+ return format(results, this._docs, {
+ includeMatches: includeMatches,
+ includeScore: includeScore
+ });
+ }
+ }, {
+ key: "_searchStringList",
+ value: function _searchStringList(query) {
+ var searcher = createSearcher(query, this.options);
+ var records = this._myIndex.records;
+ var results = [];
+
+ // Iterate over every string in the index
+ records.forEach(function (_ref2) {
+ var text = _ref2.v,
+ idx = _ref2.i,
+ norm = _ref2.n;
+ if (!isDefined(text)) {
+ return;
+ }
+ var _searcher$searchIn = searcher.searchIn(text),
+ isMatch = _searcher$searchIn.isMatch,
+ score = _searcher$searchIn.score,
+ indices = _searcher$searchIn.indices;
+ if (isMatch) {
+ results.push({
+ item: text,
+ idx: idx,
+ matches: [{
+ score: score,
+ value: text,
+ norm: norm,
+ indices: indices
+ }]
+ });
+ }
+ });
+ return results;
+ }
+ }, {
+ key: "_searchLogical",
+ value: function _searchLogical(query) {
+ {
+ throw new Error(LOGICAL_SEARCH_UNAVAILABLE);
+ }
+ }
+ }, {
+ key: "_searchObjectList",
+ value: function _searchObjectList(query) {
+ var _this2 = this;
+ var searcher = createSearcher(query, this.options);
+ var _this$_myIndex = this._myIndex,
+ keys = _this$_myIndex.keys,
+ records = _this$_myIndex.records;
+ var results = [];
+
+ // List is Array
+ records.forEach(function (_ref5) {
+ var item = _ref5.$,
+ idx = _ref5.i;
+ if (!isDefined(item)) {
+ return;
+ }
+ var matches = [];
+
+ // Iterate over every key (i.e, path), and fetch the value at that key
+ keys.forEach(function (key, keyIndex) {
+ matches.push.apply(matches, _toConsumableArray(_this2._findMatches({
+ key: key,
+ value: item[keyIndex],
+ searcher: searcher
+ })));
+ });
+ if (matches.length) {
+ results.push({
+ idx: idx,
+ item: item,
+ matches: matches
+ });
+ }
+ });
+ return results;
+ }
+ }, {
+ key: "_findMatches",
+ value: function _findMatches(_ref6) {
+ var key = _ref6.key,
+ value = _ref6.value,
+ searcher = _ref6.searcher;
+ if (!isDefined(value)) {
+ return [];
+ }
+ var matches = [];
+ if (isArray(value)) {
+ value.forEach(function (_ref7) {
+ var text = _ref7.v,
+ idx = _ref7.i,
+ norm = _ref7.n;
+ if (!isDefined(text)) {
+ return;
+ }
+ var _searcher$searchIn2 = searcher.searchIn(text),
+ isMatch = _searcher$searchIn2.isMatch,
+ score = _searcher$searchIn2.score,
+ indices = _searcher$searchIn2.indices;
+ if (isMatch) {
+ matches.push({
+ score: score,
+ key: key,
+ value: text,
+ idx: idx,
+ norm: norm,
+ indices: indices
+ });
+ }
+ });
+ } else {
+ var text = value.v,
+ norm = value.n;
+ var _searcher$searchIn3 = searcher.searchIn(text),
+ isMatch = _searcher$searchIn3.isMatch,
+ score = _searcher$searchIn3.score,
+ indices = _searcher$searchIn3.indices;
+ if (isMatch) {
+ matches.push({
+ score: score,
+ key: key,
+ value: text,
+ norm: norm,
+ indices: indices
+ });
+ }
+ }
+ return matches;
+ }
+ }]);
+ return Fuse;
+ }();
+
+ Fuse$1.version = '7.0.0';
+ Fuse$1.createIndex = createIndex;
+ Fuse$1.parseIndex = parseIndex;
+ Fuse$1.config = Config;
+ {
+ Fuse$1.parseQuery = parse;
+ }
+ var Fuse = Fuse$1;
+
+ return Fuse;
+
+}));
diff --git a/web_responsive/static/src/lib/fuse/fuse.basic.min.js b/web_responsive/static/src/lib/fuse/fuse.basic.min.js
new file mode 100644
index 000000000..745772143
--- /dev/null
+++ b/web_responsive/static/src/lib/fuse/fuse.basic.min.js
@@ -0,0 +1,9 @@
+/**
+ * Fuse.js v7.0.0 - Lightweight fuzzy-search (http://fusejs.io)
+ *
+ * Copyright (c) 2023 Kiro Risk (http://kiro.me)
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n0&&void 0!==arguments[0]?arguments[0]:{},n=t.getFn,i=void 0===n?O.getFn:n,o=t.fieldNormWeight,a=void 0===o?O.fieldNormWeight:o;r(this,e),this.norm=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:3,n=new Map,r=Math.pow(10,t);return{get:function(t){var i=t.match(j).length;if(n.has(i))return n.get(i);var o=1/Math.pow(i,.5*e),a=parseFloat(Math.round(o*r)/r);return n.set(i,a),a},clear:function(){n.clear()}}}(a,3),this.getFn=i,this.isCreated=!1,this.setIndexRecords()}return o(e,[{key:"setSources",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.docs=e}},{key:"setIndexRecords",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.records=e}},{key:"setKeys",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.keys=t,this._keysMap={},t.forEach((function(t,n){e._keysMap[t.id]=n}))}},{key:"create",value:function(){var e=this;!this.isCreated&&this.docs.length&&(this.isCreated=!0,f(this.docs[0])?this.docs.forEach((function(t,n){e._addString(t,n)})):this.docs.forEach((function(t,n){e._addObject(t,n)})),this.norm.clear())}},{key:"add",value:function(e){var t=this.size();f(e)?this._addString(e,t):this._addObject(e,t)}},{key:"removeAt",value:function(e){this.records.splice(e,1);for(var t=e,n=this.size();t2&&void 0!==arguments[2]?arguments[2]:{},r=n.getFn,i=void 0===r?O.getFn:r,o=n.fieldNormWeight,a=void 0===o?O.fieldNormWeight:o,c=new A({getFn:i,fieldNormWeight:a});return c.setKeys(e.map(x)),c.setSources(t),c.create(),c}function I(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.errors,r=void 0===n?0:n,i=t.currentLocation,o=void 0===i?0:i,a=t.expectedLocation,c=void 0===a?0:a,s=t.distance,h=void 0===s?O.distance:s,u=t.ignoreLocation,l=void 0===u?O.ignoreLocation:u,d=r/e.length;if(l)return d;var f=Math.abs(c-o);return h?d+f/h:f?1:d}var F=32;function C(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},i=r.location,o=void 0===i?O.location:i,a=r.distance,c=void 0===a?O.distance:a,s=r.threshold,h=void 0===s?O.threshold:s,u=r.findAllMatches,l=void 0===u?O.findAllMatches:u,d=r.minMatchCharLength,f=void 0===d?O.minMatchCharLength:d,v=r.includeMatches,g=void 0===v?O.includeMatches:v,y=r.ignoreLocation,p=void 0===y?O.ignoreLocation:y;if(t.length>F)throw new Error("Pattern length exceeds max of ".concat(F,"."));for(var m,b=t.length,k=e.length,M=Math.max(0,Math.min(o,k)),w=h,x=M,L=f>1||g,S=L?Array(k):[];(m=e.indexOf(t,x))>-1;){var _=I(t,{currentLocation:m,expectedLocation:M,distance:c,ignoreLocation:p});if(w=Math.min(_,w),x=m+b,L)for(var j=0;j=$;z-=1){var J=z-1,R=n[e.charAt(J)];if(L&&(S[J]=+!!R),K[z]=(K[z+1]<<1|1)&R,P&&(K[z]|=(A[z+1]|A[z])<<1|1|A[z+1]),K[z]&N&&(E=I(t,{errors:P,currentLocation:J,expectedLocation:M,distance:c,ignoreLocation:p}))<=w){if(w=E,(x=J)<=M)break;$=Math.max(1,2*M-x)}}if(I(t,{errors:P+1,currentLocation:M,expectedLocation:M,distance:c,ignoreLocation:p})>w)break;A=K}var U={isMatch:x>=0,score:Math.max(.001,E)};if(L){var B=function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:O.minMatchCharLength,n=[],r=-1,i=-1,o=0,a=e.length;o=t&&n.push([r,i]),r=-1)}return e[o-1]&&o-r>=t&&n.push([r,o-1]),n}(S,f);B.length?g&&(U.indices=B):U.isMatch=!1}return U}function N(e){for(var t={},n=0,r=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},o=i.location,a=void 0===o?O.location:o,c=i.threshold,s=void 0===c?O.threshold:c,h=i.distance,u=void 0===h?O.distance:h,l=i.includeMatches,d=void 0===l?O.includeMatches:l,f=i.findAllMatches,v=void 0===f?O.findAllMatches:f,g=i.minMatchCharLength,y=void 0===g?O.minMatchCharLength:g,p=i.isCaseSensitive,m=void 0===p?O.isCaseSensitive:p,b=i.ignoreLocation,k=void 0===b?O.ignoreLocation:b;if(r(this,e),this.options={location:a,threshold:s,distance:u,includeMatches:d,findAllMatches:v,minMatchCharLength:y,isCaseSensitive:m,ignoreLocation:k},this.pattern=m?t:t.toLowerCase(),this.chunks=[],this.pattern.length){var M=function(e,t){n.chunks.push({pattern:e,alphabet:N(e),startIndex:t})},w=this.pattern.length;if(w>F){for(var x=0,L=w%F,S=w-L;x-1&&(n.refIndex=e.idx),t.matches.push(n)}}))}function D(e,t){t.score=e.score}var K=function(){function e(n){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},o=arguments.length>2?arguments[2]:void 0;if(r(this,e),this.options=t(t({},O),i),this.options.useExtendedSearch)throw new Error("Extended search is not available");this._keyStore=new w(this.options.keys),this.setCollection(n,o)}return o(e,[{key:"setCollection",value:function(e,t){if(this._docs=e,t&&!(t instanceof A))throw new Error("Incorrect 'index' type");this._myIndex=t||E(this.options.keys,this._docs,{getFn:this.options.getFn,fieldNormWeight:this.options.fieldNormWeight})}},{key:"add",value:function(e){y(e)&&(this._docs.push(e),this._myIndex.add(e))}},{key:"remove",value:function(){for(var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){return!1},t=[],n=0,r=this._docs.length;n1&&void 0!==arguments[1]?arguments[1]:{}).limit,n=void 0===t?-1:t,r=this.options,i=r.includeMatches,o=r.includeScore,a=r.shouldSort,c=r.sortFn,s=r.ignoreFieldNorm,h=f(e)?f(this._docs[0])?this._searchStringList(e):this._searchObjectList(e):this._searchLogical(e);return function(e,t){var n=t.ignoreFieldNorm,r=void 0===n?O.ignoreFieldNorm:n;e.forEach((function(e){var t=1;e.matches.forEach((function(e){var n=e.key,i=e.norm,o=e.score,a=n?n.weight:null;t*=Math.pow(0===o&&a?Number.EPSILON:o,(a||1)*(r?1:i))})),e.score=t}))}(h,{ignoreFieldNorm:s}),a&&h.sort(c),v(n)&&n>-1&&(h=h.slice(0,n)),function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.includeMatches,i=void 0===r?O.includeMatches:r,o=n.includeScore,a=void 0===o?O.includeScore:o,c=[];return i&&c.push($),a&&c.push(D),e.map((function(e){var n=e.idx,r={item:t[n],refIndex:n};return c.length&&c.forEach((function(t){t(e,r)})),r}))}(h,this._docs,{includeMatches:i,includeScore:o})}},{key:"_searchStringList",value:function(e){var t=T(e,this.options),n=this._myIndex.records,r=[];return n.forEach((function(e){var n=e.v,i=e.i,o=e.n;if(y(n)){var a=t.searchIn(n),c=a.isMatch,s=a.score,h=a.indices;c&&r.push({item:n,idx:i,matches:[{score:s,value:n,norm:o,indices:h}]})}})),r}},{key:"_searchLogical",value:function(e){throw new Error("Logical search is not available")}},{key:"_searchObjectList",value:function(e){var t=this,n=T(e,this.options),r=this._myIndex,i=r.keys,o=r.records,a=[];return o.forEach((function(e){var r=e.$,o=e.i;if(y(r)){var s=[];i.forEach((function(e,i){s.push.apply(s,c(t._findMatches({key:e,value:r[i],searcher:n})))})),s.length&&a.push({idx:o,item:r,matches:s})}})),a}},{key:"_findMatches",value:function(e){var t=e.key,n=e.value,r=e.searcher;if(!y(n))return[];var i=[];if(u(n))n.forEach((function(e){var n=e.v,o=e.i,a=e.n;if(y(n)){var c=r.searchIn(n),s=c.isMatch,h=c.score,u=c.indices;s&&i.push({score:h,key:t,value:n,idx:o,norm:a,indices:u})}}));else{var o=n.v,a=n.n,c=r.searchIn(o),s=c.isMatch,h=c.score,l=c.indices;s&&i.push({score:h,key:t,value:o,norm:a,indices:l})}return i}}]),e}();return K.version="7.0.0",K.createIndex=E,K.parseIndex=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.getFn,r=void 0===n?O.getFn:n,i=t.fieldNormWeight,o=void 0===i?O.fieldNormWeight:i,a=e.keys,c=e.records,s=new A({getFn:r,fieldNormWeight:o});return s.setKeys(a),s.setIndexRecords(c),s},K.config=O,K},"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).Fuse=t();
\ No newline at end of file
diff --git a/web_responsive/static/src/views/form/form_controller.esm.js b/web_responsive/static/src/views/form/form_controller.esm.js
deleted file mode 100644
index c284909d8..000000000
--- a/web_responsive/static/src/views/form/form_controller.esm.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/** @odoo-module */
-/* Copyright 2023 Onestein - Anjeel Haria
- * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
-
-import {patch} from "@web/core/utils/patch";
-import {FormController} from "@web/views/form/form_controller";
-
-// Patch FormController to always load attachment alongwith the chatter on the side bar
-patch(FormController.prototype, "web_responsive.FormController", {
- setup() {
- this._super();
- this.hasAttachmentViewerInArch = false;
- },
-});
diff --git a/web_responsive/static/src/views/form/form_controller.scss b/web_responsive/static/src/views/form/form_controller.scss
index 71843fe68..75310c3ae 100644
--- a/web_responsive/static/src/views/form/form_controller.scss
+++ b/web_responsive/static/src/views/form/form_controller.scss
@@ -1,9 +1,10 @@
/* Copyright 2023 Tecnativa - Carlos Roca
+ * Copyright 2023 Taras Shabaranskyi
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
// Many2one li items with wrap
.o_field_many2one_selection {
- ul.ui-autocomplete .dropdown-item.ui-menu-item-wrapper {
+ .o-autocomplete--dropdown-menu .ui-menu-item-wrapper {
white-space: initial;
}
}
diff --git a/web_responsive/static/src/views/form/form_statusbar.scss b/web_responsive/static/src/views/form/form_statusbar.scss
new file mode 100644
index 000000000..7f7084661
--- /dev/null
+++ b/web_responsive/static/src/views/form/form_statusbar.scss
@@ -0,0 +1,21 @@
+/* Copyright 2023 Taras Shabaranskyi
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+
+.o_xxl_form_view {
+ .o_form_sheet_bg {
+ overflow: unset;
+
+ .o_form_sheet {
+ overflow: auto;
+
+ &::-webkit-scrollbar {
+ width: 10px;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background: $o-brand-odoo;
+ border-radius: 6px;
+ }
+ }
+ }
+}
diff --git a/web_responsive/static/src/views/form/status_bar_buttons.xml b/web_responsive/static/src/views/form/status_bar_buttons.xml
new file mode 100644
index 000000000..9d8cb8ff1
--- /dev/null
+++ b/web_responsive/static/src/views/form/status_bar_buttons.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ 'shift+a'
+
+
+
+
+ Action
+
+
+
+
+
diff --git a/web_responsive/static/tests/apps_menu_search_tests.esm.js b/web_responsive/static/tests/apps_menu_search_tests.esm.js
new file mode 100644
index 000000000..4832c4f5d
--- /dev/null
+++ b/web_responsive/static/tests/apps_menu_search_tests.esm.js
@@ -0,0 +1,54 @@
+/** @odoo-module **/
+/* global QUnit */
+/* eslint init-declarations: "warn" */
+/* Copyright 2023 Taras Shabaranskyi
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+
+import {click, getFixture, mount, patchWithCleanup} from "@web/../tests/helpers/utils";
+import {Component, xml} from "@odoo/owl";
+import {makeTestEnv} from "@web/../tests/helpers/mock_env";
+import {actionService} from "@web/webclient/actions/action_service";
+import {browser} from "@web/core/browser/browser";
+import {menuService} from "@web/webclient/menus/menu_service";
+import {notificationService} from "@web/core/notifications/notification_service";
+import {NavBar} from "@web/webclient/navbar/navbar";
+import {registry} from "@web/core/registry";
+import {hotkeyService} from "@web/core/hotkeys/hotkey_service";
+import {uiService} from "@web/core/ui/ui_service";
+
+const serviceRegistry = registry.category("services");
+
+class MySystrayItem extends Component {}
+
+MySystrayItem.template = xml`my item `;
+let baseConfig;
+let target;
+
+QUnit.module("AppsMenu Search", {
+ async beforeEach() {
+ target = getFixture();
+ serviceRegistry.add("menu", menuService);
+ serviceRegistry.add("action", actionService);
+ serviceRegistry.add("notification", notificationService);
+ serviceRegistry.add("hotkey", hotkeyService);
+ serviceRegistry.add("ui", uiService);
+ patchWithCleanup(browser, {
+ setTimeout: (handler, delay, ...args) => handler(...args),
+ clearTimeout: () => undefined,
+ });
+ const menus = {
+ root: {id: "root", children: [1, 2], name: "root", appID: "root"},
+ 1: {id: 1, children: [], name: "App0", appID: 1, xmlid: "menu_1"},
+ 2: {id: 2, children: [], name: "App1", appID: 2, xmlid: "menu_2"},
+ };
+ const serverData = {menus};
+ baseConfig = {serverData};
+ },
+});
+
+QUnit.test("can be rendered", async (assert) => {
+ const env = await makeTestEnv(baseConfig);
+ await mount(NavBar, target, {env});
+ await click(target, "button.o_grid_apps_menu__button");
+ assert.containsOnce(target, ".app-menu-container .search-input");
+});
diff --git a/web_responsive/static/tests/apps_menu_tests.esm.js b/web_responsive/static/tests/apps_menu_tests.esm.js
new file mode 100644
index 000000000..6af7a5e00
--- /dev/null
+++ b/web_responsive/static/tests/apps_menu_tests.esm.js
@@ -0,0 +1,87 @@
+/** @odoo-module **/
+/* global QUnit */
+/* eslint init-declarations: "warn" */
+/* Copyright 2023 Taras Shabaranskyi
+ * License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
+
+import {
+ click,
+ getFixture,
+ mount,
+ nextTick,
+ patchWithCleanup,
+} from "@web/../tests/helpers/utils";
+import {Component, xml} from "@odoo/owl";
+import {makeTestEnv} from "@web/../tests/helpers/mock_env";
+import {actionService} from "@web/webclient/actions/action_service";
+import {browser} from "@web/core/browser/browser";
+import {menuService} from "@web/webclient/menus/menu_service";
+import {notificationService} from "@web/core/notifications/notification_service";
+import {NavBar} from "@web/webclient/navbar/navbar";
+import {registry} from "@web/core/registry";
+import {hotkeyService} from "@web/core/hotkeys/hotkey_service";
+import {uiService} from "@web/core/ui/ui_service";
+
+const serviceRegistry = registry.category("services");
+
+class MySystrayItem extends Component {}
+
+MySystrayItem.template = xml`my item `;
+let baseConfig;
+let target;
+
+QUnit.module("AppsMenu", {
+ async beforeEach() {
+ target = getFixture();
+ serviceRegistry.add("menu", menuService);
+ serviceRegistry.add("action", actionService);
+ serviceRegistry.add("notification", notificationService);
+ serviceRegistry.add("hotkey", hotkeyService);
+ serviceRegistry.add("ui", uiService);
+ patchWithCleanup(browser, {
+ setTimeout: (handler, delay, ...args) => handler(...args),
+ clearTimeout: () => undefined,
+ });
+ const menus = {
+ root: {id: "root", children: [1, 2], name: "root", appID: "root"},
+ 1: {id: 1, children: [], name: "App0", appID: 1, xmlid: "menu_1"},
+ 2: {id: 2, children: [], name: "App1", appID: 2, xmlid: "menu_2"},
+ };
+ const serverData = {menus};
+ baseConfig = {serverData};
+ },
+});
+
+QUnit.test("can be rendered", async (assert) => {
+ const env = await makeTestEnv(baseConfig);
+ await mount(NavBar, target, {env});
+ assert.containsOnce(
+ target,
+ ".o_grid_apps_menu button.o_grid_apps_menu__button",
+ "1 apps menu button present"
+ );
+});
+
+QUnit.test("can be opened and closed", async (assert) => {
+ const env = await makeTestEnv(baseConfig);
+ await mount(NavBar, target, {env});
+ await click(target, "button.o_grid_apps_menu__button");
+ await nextTick();
+ assert.containsOnce(target, ".o-app-menu-list");
+ await click(target, "button.o_grid_apps_menu__button");
+ await nextTick();
+ assert.containsNone(target, ".o-app-menu-list");
+});
+
+QUnit.test("can be active", async (assert) => {
+ const env = await makeTestEnv(baseConfig);
+ await mount(NavBar, target, {env});
+ await click(target, "button.o_grid_apps_menu__button");
+ await nextTick();
+ env.services.menu.setCurrentMenu(1);
+ await nextTick();
+ assert.containsOnce(target, '.o-app-menu-item.active[data-menu-xmlid="menu_1"]');
+ env.services.menu.setCurrentMenu(2);
+ await nextTick();
+ assert.containsOnce(target, '.o-app-menu-item.active[data-menu-xmlid="menu_2"]');
+});
diff --git a/web_responsive/tests/__init__.py b/web_responsive/tests/__init__.py
new file mode 100644
index 000000000..1961bf280
--- /dev/null
+++ b/web_responsive/tests/__init__.py
@@ -0,0 +1,4 @@
+# Copyright 2023 Taras Shabaranskyi
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
+
+from . import test_ir_http
diff --git a/web_responsive/tests/test_ir_http.py b/web_responsive/tests/test_ir_http.py
new file mode 100644
index 000000000..e2100762c
--- /dev/null
+++ b/web_responsive/tests/test_ir_http.py
@@ -0,0 +1,35 @@
+# Copyright 2023 Taras Shabaranskyi
+# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
+
+import json
+import re
+
+from odoo.tests import HttpCase, tagged
+
+
+@tagged("-at_install", "post_install")
+class TestIrHttp(HttpCase):
+ def _test_session_info(self, session_info):
+ apps_menu = session_info.get("apps_menu")
+ self.assertIsNotNone(apps_menu)
+ self.assertTrue("search_type" in apps_menu)
+ self.assertTrue("theme" in apps_menu)
+
+ def _find_session_info(self, line_items):
+ key = "odoo.__session_info__ = "
+ line = next(filter(lambda item: key in item, line_items), None)
+ self.assertIsNotNone(line)
+ match = re.match(rf".*{key}(.*);", line)
+ self.assertIsNotNone(match)
+ return match.group(1)
+
+ def test_session_info(self):
+ self.authenticate("admin", "admin")
+ r = self.url_open("/web")
+ self.assertEqual(r.status_code, 200)
+ self.assertIsInstance(r.text, str)
+ line_items = r.text.splitlines()
+ self.assertTrue(bool(line_items))
+ session_info_str = self._find_session_info(line_items)
+ self.assertIsInstance(session_info_str, str)
+ self._test_session_info(json.loads(session_info_str))
diff --git a/web_responsive/views/res_users_views.xml b/web_responsive/views/res_users_views.xml
new file mode 100644
index 000000000..8972a65f3
--- /dev/null
+++ b/web_responsive/views/res_users_views.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
diff --git a/web_responsive/views/web.xml b/web_responsive/views/web.xml
deleted file mode 100644
index bf0783e38..000000000
--- a/web_responsive/views/web.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-