[MIG] web_responsive: Migration to 14.0

This commit is contained in:
Sergey Shebanin
2021-02-11 15:07:56 +03:00
committed by Taras Shabaranskyi
parent 94c8035200
commit 655ea0e5a7
22 changed files with 1473 additions and 670 deletions

View File

@@ -1,300 +0,0 @@
/* Copyright Odoo S.A.
* Ported to 13.0 by Copyright 2020 Tecnativa - Alexandre Díaz
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive.Discuss", function(require) {
"use strict";
const config = require("web.config");
if (!config.device.isMobile) {
return;
}
const core = require("web.core");
const Discuss = require("mail.Discuss");
const QWeb = core.qweb;
Discuss.include({
contentTemplate: "mail.discuss_mobile",
events: Object.assign({}, Discuss.prototype.events, {
"click .o_mail_mobile_tab": "_onClickMobileTab",
"click .o_mailbox_inbox_item": "_onClickMobileMailboxItem",
"click .o_mail_preview": "_onClickMobileMailPreview",
}),
/**
* @override
*/
init: function() {
this._super.apply(this, arguments);
this._currentState = this._defaultThreadID;
},
/**
* @override
*/
start: function() {
this._$mainContent = this.$(".o_mail_discuss_content");
return this._super
.apply(this, arguments)
.then(this._updateControlPanel.bind(this));
},
/**
* @override
*/
on_attach_callback: function() {
if (this._thread && this._isInInboxTab()) {
this._threadWidget.scrollToPosition(
this._threadsScrolltop[this._thread.getID()]
);
}
},
/**
* @override
*/
on_detach_callback: function() {
if (this._isInInboxTab()) {
this._threadsScrolltop[
this._thread.getID()
] = this._threadWidget.getScrolltop();
}
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* @private
* @returns {Boolean} true iff we currently are in the Inbox tab
*/
_isInInboxTab: function() {
return _.contains(["mailbox_inbox", "mailbox_starred"], this._currentState);
},
/**
* @override
* @private
*/
_renderButtons: function() {
this._super.apply(this, arguments);
_.each(["dm_chat", "multi_user_channel"], type => {
const selector = ".o_mail_discuss_button_" + type;
this.$buttons.on("click", selector, this._onAddThread.bind(this));
});
},
/**
* Overrides to only store the thread state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed thread
*
* @override
* @private
*/
_restoreThreadState: function() {
if (this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* Overrides to toggle the visibility of the tabs when a message is selected
*
* @override
* @private
*/
_selectMessage: function() {
this._super.apply(this, arguments);
this.$(".o_mail_mobile_tabs").addClass("o_hidden");
},
/**
* @override
* @private
*/
_setThread: function(threadID) {
const thread = this.call("mail_service", "getThread", threadID);
this._thread = thread;
if (thread.getType() !== "mailbox") {
this.call("mail_service", "openThreadWindow", threadID);
return Promise.resolve();
}
return this._super.apply(this, arguments);
},
/**
* Overrides to only store the thread state if we are in the Inbox tab, as
* this is the only tab in which we actually have a displayed thread
*
* @override
* @private
*/
_storeThreadState: function() {
if (this._thread && this._isInInboxTab()) {
this._super.apply(this, arguments);
}
},
/**
* Overrides to toggle the visibility of the tabs when a message is
* unselected
*
* @override
* @private
*/
_unselectMessage: function() {
this._super.apply(this, arguments);
this.$(".o_mail_mobile_tabs").removeClass("o_hidden");
},
/**
* @override
* @private
*/
_updateThreads: function() {
return this._updateContent(this._currentState);
},
/**
* Redraws the content of the client action according to its current state.
*
* @private
* @param {String} type the thread's type to display (e.g. 'mailbox_inbox',
* 'mailbox_starred', 'dm_chat'...).
* @returns {Promise}
*/
_updateContent: function(type) {
const inMailbox = type === "mailbox_inbox" || type === "mailbox_starred";
if (!inMailbox && this._isInInboxTab()) {
// We're leaving the inbox, so store the thread scrolltop
this._storeThreadState();
}
const previouslyInInbox = this._isInInboxTab();
this._currentState = type;
// Fetch content to display
let def = false;
if (inMailbox) {
def = this._fetchAndRenderThread();
} else {
const allChannels = this.call("mail_service", "getChannels");
const channels = _.filter(allChannels, function(channel) {
return channel.getType() === type;
});
def = this.call("mail_service", "getChannelPreviews", channels);
}
return def.then(previews => {
// Update content
if (inMailbox) {
if (!previouslyInInbox) {
this.$(".o_mail_discuss_tab_pane").remove();
this._$mainContent.append(this._threadWidget.$el);
this._$mainContent.append(this._extendedComposer.$el);
}
this._restoreThreadState();
} else {
this._threadWidget.$el.detach();
this._extendedComposer.$el.detach();
const $content = $(
QWeb.render("mail.discuss.MobileTabPane", {
previews: previews,
type: type,
})
);
this._prepareAddThreadInput(
$content.find(".o_mail_add_thread input"),
type
);
this._$mainContent.html($content);
}
// Update control panel
this.$buttons
.find("button")
.removeClass("d-block")
.addClass("d-none");
this.$buttons
.find(".o_mail_discuss_button_" + type)
.removeClass("d-none")
.addClass("d-block");
this.$buttons
.find(".o_mail_discuss_button_mark_all_read")
.toggleClass("d-none", type !== "mailbox_inbox")
.toggleClass("d-block", type === "mailbox_inbox");
this.$buttons
.find(".o_mail_discuss_button_unstar_all")
.toggleClass("d-none", type !== "mailbox_starred")
.toggleClass("d-block", type === "mailbox_starred");
// Update Mailbox page buttons
if (inMailbox) {
this.$(".o_mail_discuss_mobile_mailboxes_buttons").removeClass(
"o_hidden"
);
this.$(".o_mailbox_inbox_item")
.removeClass("btn-primary")
.addClass("btn-secondary");
this.$(".o_mailbox_inbox_item[data-type=" + type + "]")
.removeClass("btn-secondary")
.addClass("btn-primary");
} else {
this.$(".o_mail_discuss_mobile_mailboxes_buttons").addClass(
"o_hidden"
);
}
// Update bottom buttons
this.$(".o_mail_mobile_tab").removeClass("active");
// Mailbox_inbox and mailbox_starred share the same tab
const type_n = type === "mailbox_starred" ? "mailbox_inbox" : type;
this.$(".o_mail_mobile_tab[data-type=" + type_n + "]").addClass(
"active"
);
});
},
// --------------------------------------------------------------------------
// Handlers
// --------------------------------------------------------------------------
/**
* @override
* @private
*/
_onAddThread: function() {
this.$(".o_mail_add_thread")
.show()
.find("input")
.focus();
},
/**
* Switches to the clicked thread in the Inbox page (Inbox or Starred).
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileMailboxItem: function(ev) {
const mailboxID = $(ev.currentTarget).data("type");
this._setThread(mailboxID);
this._updateContent(this._thread.getID());
},
/**
* Switches to another tab.
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileTab: function(ev) {
const type = $(ev.currentTarget).data("type");
if (type === "mailbox") {
const inbox = this.call("mail_service", "getMailbox", "inbox");
this._setThread(inbox);
}
this._updateContent(type);
},
/**
* Opens a thread in a chat window (full screen in mobile).
*
* @private
* @param {MouseEvent} ev
*/
_onClickMobileMailPreview: function(ev) {
const threadID = $(ev.currentTarget).data("preview-id");
this.call("mail_service", "openThreadWindow", threadID);
},
});
});

View File

@@ -0,0 +1,494 @@
odoo.define("web_responsive.KanbanRendererMobile", function (require) {
"use strict";
/**
* The purpose of this file is to improve the UX of grouped kanban views in
* mobile. It includes the KanbanRenderer (in mobile only) to only display one
* column full width, and enables the swipe to browse to the other columns.
* Moreover, records in columns are lazy-loaded.
*/
var config = require("web.config");
var core = require("web.core");
var KanbanRenderer = require("web.KanbanRenderer");
var KanbanView = require("web.KanbanView");
var KanbanQuickCreate = require("web.kanban_column_quick_create");
var _t = core._t;
var qweb = core.qweb;
if (!config.device.isMobile) {
return;
}
KanbanQuickCreate.include({
init() {
this._super.apply(this, arguments);
this.isMobile = true;
},
});
KanbanView.include({
init() {
this._super.apply(this, arguments);
this.jsLibs.push("/web/static/lib/jquery.touchSwipe/jquery.touchSwipe.js");
},
});
KanbanRenderer.include({
custom_events: _.extend({}, KanbanRenderer.prototype.custom_events || {}, {
quick_create_column_created: "_onColumnAdded",
}),
events: _.extend({}, KanbanRenderer.prototype.events, {
"click .o_kanban_mobile_tab": "_onMobileTabClicked",
"click .o_kanban_mobile_add_column": "_onMobileQuickCreateClicked",
}),
ANIMATE: true, // Allows to disable animations for the tests
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.activeColumnIndex = 0; // Index of the currently displayed column
this._scrollPosition = null;
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to restore the scroll position like it was
* when the renderer has been last detached.
*
* @override
*/
on_attach_callback: function () {
if (
this._scrollPosition &&
this.state.groupedBy.length &&
this.widgets.length
) {
var $column = this.widgets[this.activeColumnIndex].$el;
$column.scrollLeft(this._scrollPosition.left);
$column.scrollTop(this._scrollPosition.top);
}
this._computeTabPosition();
this._super.apply(this, arguments);
},
/**
* As this renderer defines its own scrolling area (the column in grouped
* mode), we override this hook to store the scroll position, so that we can
* restore it if the renderer is re-attached to the DOM later.
*
* @override
*/
on_detach_callback: function () {
if (this.state.groupedBy.length && this.widgets.length) {
var $column = this.widgets[this.activeColumnIndex].$el;
this._scrollPosition = {
left: $column.scrollLeft(),
top: $column.scrollTop(),
};
} else {
this._scrollPosition = null;
}
this._super.apply(this, arguments);
},
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
/**
* Displays the quick create record in the active column
* override to open quick create record in current active column
*
* @override
* @returns {Promise}
*/
addQuickCreate: function () {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this._onMobileQuickCreateClicked();
}
return this.widgets[this.activeColumnIndex].addQuickCreate();
},
/**
* Overrides to restore the left property and the scrollTop on the updated
* column, and to enable the swipe handlers
*
* @override
*/
updateColumn: function (localID) {
var index = _.findIndex(this.widgets, {db_id: localID});
var $column = this.widgets[index].$el;
var scrollTop = $column.scrollTop();
return (
this._super
.apply(this, arguments)
.then(() => this._layoutUpdate(false))
// Required when clicking on 'Load More'
.then(() => $column.scrollTop(scrollTop))
.then(() => this._enableSwipe())
);
},
// --------------------------------------------------------------------------
// Private
// --------------------------------------------------------------------------
/**
* Check if we use the quick create on mobile
* @returns {Boolean}
* @private
*/
_canCreateColumn: function () {
return this.quickCreateEnabled && this.quickCreate && this.widgets.length;
},
/**
* Update the columns positions
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_computeColumnPosition: function (animate) {
if (this.widgets.length) {
// Check rtl to compute correct css value
const rtl = _t.database.parameters.direction === "rtl";
// Display all o_kanban_group
this.$(".o_kanban_group").show();
const $columnAfter = this._toNode(
this.widgets.filter(
(widget, index) => index > this.activeColumnIndex
)
);
const promiseAfter = this._updateColumnCss(
$columnAfter,
rtl ? {right: "100%"} : {left: "100%"},
animate
);
const $columnBefore = this._toNode(
this.widgets.filter(
(widget, index) => index < this.activeColumnIndex
)
);
const promiseBefore = this._updateColumnCss(
$columnBefore,
rtl ? {right: "-100%"} : {left: "-100%"},
animate
);
const $columnCurrent = this._toNode(
this.widgets.filter(
(widget, index) => index === this.activeColumnIndex
)
);
const promiseCurrent = this._updateColumnCss(
$columnCurrent,
rtl ? {right: "0%"} : {left: "0%"},
animate
);
promiseAfter
.then(promiseBefore)
.then(promiseCurrent)
.then(() => {
$columnAfter.hide();
$columnBefore.hide();
});
}
},
/**
* Define the o_current class to the current selected kanban (column & tab)
*
* @private
*/
_computeCurrentColumn: function () {
if (this.widgets.length) {
var column = this.widgets[this.activeColumnIndex];
if (!column) {
return;
}
var columnID = column.id || column.db_id;
this.$(
".o_kanban_mobile_tab.o_current, .o_kanban_group.o_current"
).removeClass("o_current");
this.$(
'.o_kanban_group[data-id="' +
columnID +
'"], ' +
'.o_kanban_mobile_tab[data-id="' +
columnID +
'"]'
).addClass("o_current");
}
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabPosition: function () {
this._computeTabJustification();
this._computeTabScrollPosition();
},
/**
* Update the tabs positions
*
* @private
*/
_computeTabScrollPosition: function () {
if (this.widgets.length) {
var lastItemIndex = this.widgets.length - 1;
var moveToIndex = this.activeColumnIndex;
var scrollToLeft = 0;
for (var i = 0; i < moveToIndex; i++) {
var columnWidth = this._getTabWidth(this.widgets[i]);
// Apply
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
var partialWidth = 0.75;
scrollToLeft += columnWidth * partialWidth;
} else {
scrollToLeft += columnWidth;
}
}
// Apply the scroll x on the tabs
// XXX in case of RTL, should we use scrollRight?
this.$(".o_kanban_mobile_tabs").scrollLeft(scrollToLeft);
}
},
/**
* Compute the justify content of the kanban tab headers
*
* @private
*/
_computeTabJustification: function () {
if (this.widgets.length) {
var self = this;
// Use to compute the sum of the width of all tab
var widthChilds = this.widgets.reduce(function (total, column) {
return total + self._getTabWidth(column);
}, 0);
// Apply a space around between child if the parent length is higher then the sum of the child width
var $tabs = this.$(".o_kanban_mobile_tabs");
$tabs.toggleClass(
"justify-content-between",
$tabs.outerWidth() >= widthChilds
);
}
},
/**
* Enables swipe event on the current column
*
* @private
*/
_enableSwipe: function () {
var self = this;
var step = _t.database.parameters.direction === "rtl" ? -1 : 1;
this.$el.swipe({
excludedElements: ".o_kanban_mobile_tabs",
swipeLeft: function () {
var moveToIndex = self.activeColumnIndex + step;
if (moveToIndex < self.widgets.length) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
swipeRight: function () {
var moveToIndex = self.activeColumnIndex - step;
if (moveToIndex > -1) {
self._moveToGroup(moveToIndex, self.ANIMATE);
}
},
});
},
/**
* Retrieve the outerWidth of a given widget column
*
* @param {KanbanColumn} column
* @returns {integer} outerWidth of the found column
* @private
*/
_getTabWidth: function (column) {
var columnID = column.id || column.db_id;
return this.$(
'.o_kanban_mobile_tab[data-id="' + columnID + '"]'
).outerWidth();
},
/**
* Update the kanban layout
*
* @private
* @param {Boolean} [animate=false] set to true to animate
*/
_layoutUpdate: function (animate) {
this._computeCurrentColumn();
this._computeTabPosition();
this._computeColumnPosition(animate);
this._enableSwipe();
},
/**
* Moves to the given kanban column
*
* @private
* @param {integer} moveToIndex index of the column to move to
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise} resolved when the new current group has been loaded
* and displayed
*/
_moveToGroup: function (moveToIndex, animate) {
var self = this;
if (moveToIndex >= 0 && moveToIndex < this.widgets.length) {
this.activeColumnIndex = moveToIndex;
}
var column = this.widgets[this.activeColumnIndex];
this._enableSwipe();
if (!column.data.isOpen) {
this.trigger_up("column_toggle_fold", {
db_id: column.db_id,
onSuccess: () => self._layoutUpdate(animate),
});
} else {
this._layoutUpdate(animate);
}
return Promise.resolve();
},
/**
* @override
* @private
*/
_renderExampleBackground: function () {
// Override to avoid display of example background
},
/**
* @override
* @private
*/
_renderGrouped: function (fragment) {
var self = this;
var newFragment = document.createDocumentFragment();
this._super.apply(this, [newFragment]);
this.defs.push(
Promise.all(this.defs).then(function () {
var data = [];
_.each(self.state.data, function (group) {
if (!group.value) {
group = _.extend({}, group, {value: _t("Undefined")});
data.unshift(group);
} else {
data.push(group);
}
});
var kanbanColumnContainer = document.createElement("div");
kanbanColumnContainer.classList.add("o_kanban_columns_content");
kanbanColumnContainer.appendChild(newFragment);
fragment.appendChild(kanbanColumnContainer);
$(
qweb.render("KanbanView.MobileTabs", {
data: data,
quickCreateEnabled: self._canCreateColumn(),
})
).prependTo(fragment);
})
);
},
/**
* @override
* @private
*/
_renderView: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
if (self.state.groupedBy.length) {
// Force first column for kanban view, because the groupedBy can be changed
return self._moveToGroup(0);
}
if (self._canCreateColumn()) {
self._onMobileQuickCreateClicked();
}
return Promise.resolve();
});
},
/**
* Retrieve the Jquery node (.o_kanban_group) for a list of a given widgets
*
* @private
* @param widgets
* @returns {jQuery} the matching .o_kanban_group widgets
*/
_toNode: function (widgets) {
const selectorCss = widgets
.map(
(widget) =>
'.o_kanban_group[data-id="' + (widget.id || widget.db_id) + '"]'
)
.join(", ");
return this.$(selectorCss);
},
/**
* Update the given column to the updated positions
*
* @private
* @param $column The jquery column
* @param cssProperties Use to update column
* @param {Boolean} [animate=false] set to true to animate
* @returns {Promise}
*/
_updateColumnCss: function ($column, cssProperties, animate) {
if (animate) {
return new Promise((resolve) =>
$column.animate(cssProperties, "fast", resolve)
);
}
$column.css(cssProperties);
return Promise.resolve();
},
// --------------------------------------------------------------------------
// Handlers
// --------------------------------------------------------------------------
/**
* @private
*/
_onColumnAdded: function () {
this._computeTabPosition();
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
},
/**
* @private
*/
_onMobileQuickCreateClicked: function (event) {
if (event) {
event.stopPropagation();
}
this.$(".o_kanban_group").toggle();
this.quickCreate.toggleFold();
},
/**
* @private
* @param {MouseEvent} event
*/
_onMobileTabClicked: function (event) {
if (this._canCreateColumn() && !this.quickCreate.folded) {
this.quickCreate.toggleFold();
}
this._moveToGroup($(event.currentTarget).index(), true);
},
});
});

View File

@@ -1,7 +1,8 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2018 Tecnativa - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive", function(require) {
odoo.define("web_responsive", function (require) {
"use strict";
const ActionManager = require("web.ActionManager");
@@ -13,16 +14,23 @@ odoo.define("web_responsive", function(require) {
const FormRenderer = require("web.FormRenderer");
const Menu = require("web.Menu");
const RelationalFields = require("web.relational_fields");
const Chatter = require("mail.Chatter");
const ListRenderer = require("web.ListRenderer");
const DocumentViewer = require("mail.DocumentViewer");
const CalendarRenderer = require("web.CalendarRenderer");
const patchMixin = require("web.patchMixin");
const AttachmentViewer = require("mail/static/src/components/attachment_viewer/attachment_viewer.js");
const PatchableAttachmentViewer = patchMixin(AttachmentViewer);
const ControlPanel = require("web.ControlPanel");
const SearchPanel = require("web/static/src/js/views/search_panel.js");
/* global owl */
const {QWeb, Context} = owl;
const {useState, useContext} = owl.hooks;
/* Hide AppDrawer in desktop and mobile modes.
* To avoid delays in pages with a lot of DOM nodes we make
* sub-groups' with 'querySelector' to improve the performance.
*/
function closeAppDrawer() {
_.defer(function() {
_.defer(function () {
// Need close AppDrawer?
var menu_apps_dropdown = document.querySelector(".o_menu_apps .dropdown");
$(menu_apps_dropdown)
@@ -115,7 +123,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
init: function(parent, menuData) {
init: function (parent, menuData) {
this._super.apply(this, arguments);
// Keep base64 icon for main menus
for (const n in this._apps) {
@@ -130,7 +138,7 @@ odoo.define("web_responsive", function(require) {
/**
* @override
*/
start: function() {
start: function () {
this.$search_container = this.$(".search-container");
this.$search_input = this.$(".search-input input");
this.$search_results = this.$(".search-results");
@@ -142,7 +150,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_onAppsMenuItemClicked: function(ev) {
_onAppsMenuItemClicked: function (ev) {
this._super.apply(this, arguments);
ev.preventDefault();
ev.stopPropagation();
@@ -157,7 +165,7 @@ odoo.define("web_responsive", function(require) {
* @returns {Object}
* Menu definition, plus extra needed keys.
*/
_menuInfo: function(key) {
_menuInfo: function (key) {
const original = this._searchableMenus[key];
return _.extend(
{
@@ -170,7 +178,7 @@ odoo.define("web_responsive", function(require) {
/**
* Autofocus on search field on big screens.
*/
_searchFocus: function() {
_searchFocus: function () {
if (!config.device.isMobile) {
// This timeout is necessary since the menu has a 100ms fading animation
setTimeout(() => this.$search_input.focus(), 100);
@@ -180,7 +188,7 @@ odoo.define("web_responsive", function(require) {
/**
* Reset search input and results
*/
_searchReset: function() {
_searchReset: function () {
this.$search_container.removeClass("has-results");
this.$search_results.empty();
this.$search_input.val("");
@@ -189,8 +197,8 @@ odoo.define("web_responsive", function(require) {
/**
* Schedule a search on current menu items.
*/
_searchMenusSchedule: function() {
this._search_def = new Promise(resolve => {
_searchMenusSchedule: function () {
this._search_def = new Promise((resolve) => {
setTimeout(resolve, 50);
});
this._search_def.then(this._searchMenus.bind(this));
@@ -199,7 +207,7 @@ odoo.define("web_responsive", function(require) {
/**
* Search among available menu items, and render that search.
*/
_searchMenus: function() {
_searchMenus: function () {
const query = this.$search_input.val();
if (query === "") {
this.$search_container.removeClass("has-results");
@@ -224,7 +232,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {jQuery.Event} event
*/
_searchResultChosen: function(event) {
_searchResultChosen: function (event) {
event.preventDefault();
event.stopPropagation();
const $result = $(event.currentTarget),
@@ -238,7 +246,7 @@ odoo.define("web_responsive", function(require) {
previous_menu_id: data.parentId,
});
// Find app that owns the chosen menu
const app = _.find(this._apps, function(_app) {
const app = _.find(this._apps, function (_app) {
return text.indexOf(_app.name + suffix) === 0;
});
// Update navbar menus
@@ -250,7 +258,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {jQuery.Event} event
*/
_searchResultsNavigate: function(event) {
_searchResultsNavigate: function (event) {
// Find current results and active element (1st by default)
const all = this.$search_results.find(".o-menu-search-result"),
pre_focused = all.filter(".active") || $(all[0]);
@@ -301,7 +309,7 @@ odoo.define("web_responsive", function(require) {
/*
* Control if AppDrawer can be closed
*/
_hideAppsMenu: function() {
_hideAppsMenu: function () {
return !this.$("input").is(":focus");
},
});
@@ -313,7 +321,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
canBeDiscarded: function(recordID) {
canBeDiscarded: function (recordID) {
if (this.model.isDirty(recordID || this.handle)) {
closeAppDrawer();
}
@@ -332,7 +340,7 @@ odoo.define("web_responsive", function(require) {
Menu.prototype.events
),
start: function() {
start: function () {
this.$menu_toggle = this.$(".o-menu-toggle");
return this._super.apply(this, arguments);
},
@@ -340,7 +348,7 @@ odoo.define("web_responsive", function(require) {
/**
* Hide menus for current app if you're in mobile
*/
_hideMobileSubmenus: function() {
_hideMobileSubmenus: function () {
if (
config.device.isMobile &&
this.$menu_toggle.is(":visible") &&
@@ -355,7 +363,7 @@ odoo.define("web_responsive", function(require) {
*
* @param {ClickEvent} ev
*/
_onClickMenuItem: function(ev) {
_onClickMenuItem: function (ev) {
ev.stopPropagation();
},
@@ -364,7 +372,7 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_updateMenuBrand: function() {
_updateMenuBrand: function () {
if (!config.device.isMobile) {
return this._super.apply(this, arguments);
}
@@ -377,10 +385,10 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_setState: function() {
_setState: function () {
this._super.apply(this, arguments);
if (config.device.isMobile) {
_.map(this.status_information, value => {
_.map(this.status_information, (value) => {
value.fold = true;
});
}
@@ -389,7 +397,7 @@ odoo.define("web_responsive", function(require) {
// Sticky Column Selector
ListRenderer.include({
_renderView: function() {
_renderView: function () {
const self = this;
return this._super.apply(this, arguments).then(() => {
const $col_selector = self.$el.find(
@@ -402,7 +410,7 @@ odoo.define("web_responsive", function(require) {
});
},
_onToggleOptionalColumnDropdown: function(ev) {
_onToggleOptionalColumnDropdown: function (ev) {
// FIXME: For some strange reason the 'stopPropagation' call
// in the main method don't work. Invoking here the same method
// does the expected behavior... O_O!
@@ -420,17 +428,16 @@ odoo.define("web_responsive", function(require) {
*
* @override
*/
_renderHeaderButtons: function() {
_renderHeaderButtons: function () {
const $buttons = this._super.apply(this, arguments);
if (
!config.device.isMobile ||
!$buttons.is(":has(>:not(.o_invisible_modifier))")
$buttons.children("button:not(.o_invisible_modifier)").length <= 2
) {
return $buttons;
}
// $buttons must be appended by JS because all events are bound
$buttons.addClass("dropdown-menu");
const $dropdown = $(
core.qweb.render("web_responsive.MenuStatusbarButtons")
);
@@ -439,18 +446,13 @@ odoo.define("web_responsive", function(require) {
},
});
// Chatter Hide Composer
Chatter.include({
_openComposer: function(options) {
if (
this._composer &&
options.isLog === this._composer.options.isLog &&
this._composer.$el.is(":visible")
) {
this._closeComposer(false);
} else {
this._super.apply(this, arguments);
CalendarRenderer.include({
_getFullCalendarOptions: function () {
var options = this._super.apply(this, arguments);
if (config.device.isMobile) {
options.views.dayGridMonth.columnHeaderFormat = "ddd";
}
return options;
},
});
@@ -459,7 +461,7 @@ odoo.define("web_responsive", function(require) {
/**
* @override
*/
_appendController: function() {
_appendController: function () {
this._super.apply(this, arguments);
closeAppDrawer();
},
@@ -495,7 +497,7 @@ odoo.define("web_responsive", function(require) {
* @returns {keyEvent}
* Altered event object
*/
_shiftPressed: function(keyEvent) {
_shiftPressed: function (keyEvent) {
const alt = keyEvent.altKey || keyEvent.key === "Alt",
newEvent = _.extend({}, keyEvent),
shift = keyEvent.shiftKey || keyEvent.key === "Shift";
@@ -509,11 +511,11 @@ odoo.define("web_responsive", function(require) {
return newEvent;
},
_onKeyDown: function(keyDownEvent) {
_onKeyDown: function (keyDownEvent) {
return this._super(this._shiftPressed(keyDownEvent));
},
_onKeyUp: function(keyUpEvent) {
_onKeyUp: function (keyUpEvent) {
return this._super(this._shiftPressed(keyUpEvent));
},
};
@@ -522,44 +524,106 @@ odoo.define("web_responsive", function(require) {
// `KeyboardNavigationMixin` is used upstream
AbstractWebClient.include(KeyboardNavigationShiftAltMixin);
// DocumentViewer: Add support to maximize/minimize
DocumentViewer.include({
// Widget 'keydown' and 'keyup' events are only dispatched when
// this.$el is active, but now the modal have buttons that can obtain
// the focus. For this reason we now listen core events, that are
// dispatched every time.
events: _.extend(
_.omit(DocumentViewer.prototype.events, ["keydown", "keyup"]),
{
"click .o_maximize_btn": "_onClickMaximize",
"click .o_minimize_btn": "_onClickMinimize",
"shown.bs.modal": "_onShownModal",
}
),
start: function() {
core.bus.on("keydown", this, this._onKeydown);
core.bus.on("keyup", this, this._onKeyUp);
return this._super.apply(this, arguments);
},
destroy: function() {
core.bus.off("keydown", this, this._onKeydown);
core.bus.off("keyup", this, this._onKeyUp);
this._super.apply(this, arguments);
},
_onShownModal: function() {
// Disable auto-focus to allow to use controls in edit mode.
// This only affects the active modal.
// More info: https://stackoverflow.com/a/14795256
$(document).off("focusin.modal");
},
_onClickMaximize: function() {
this.$el.removeClass("o_responsive_document_viewer");
},
_onClickMinimize: function() {
this.$el.addClass("o_responsive_document_viewer");
},
// TODO: use default odoo device context when it will be realized
const deviceContext = new Context({
isMobile: config.device.isMobile,
size_class: config.device.size_class,
SIZES: config.device.SIZES,
});
window.addEventListener(
"resize",
owl.utils.debounce(() => {
const state = deviceContext.state;
if (state.isMobile !== config.device.isMobile) {
state.isMobile = !state.isMobile;
}
if (state.size_class !== config.device.size_class) {
state.size_class = config.device.size_class;
}
}, 15)
);
// Patch attachment viewer to add min/max buttons capability
PatchableAttachmentViewer.patch("web_responsive.AttachmentViewer", (T) => {
class AttachmentViewerPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
maximized: false,
});
}
// Disable auto-close to allow to use form in edit mode.
isCloseable() {
return false;
}
}
return AttachmentViewerPatchResponsive;
});
QWeb.components.AttachmentViewer = PatchableAttachmentViewer;
// Patch control panel to add states for mobile quick search
ControlPanel.patch("web_responsive.ControlPanelMobile", (T) => {
class ControlPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state = useState({
mobileSearchMode: "",
});
this.device = useContext(deviceContext);
}
}
return ControlPanelPatchResponsive;
});
// Patch search panel to add functionality for mobile view
SearchPanel.patch("web_responsive.SearchPanelMobile", (T) => {
class SearchPanelPatchResponsive extends T {
constructor() {
super(...arguments);
this.state.mobileSearch = false;
this.device = useContext(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;
}
}
return SearchPanelPatchResponsive;
});
return {
deviceContext: deviceContext,
};
});