mirror of
https://github.com/OCA/web.git
synced 2025-02-22 13:21:25 +02:00
[MIG] web_responsive: Migration to 16.0
Fix for optional dropdown checkbox in list view Made Changes for compatibility with web_chatter_position module Fix for attachment delete dialog
This commit is contained in:
committed by
Taras Shabaranskyi
parent
fa5e79acc1
commit
475d01f68a
@@ -1,569 +0,0 @@
|
||||
/* Copyright 2019 Odoo S.A.
|
||||
* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
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.
|
||||
*/
|
||||
|
||||
const config = require("web.config");
|
||||
const core = require("web.core");
|
||||
const KanbanRenderer = require("web.KanbanRenderer");
|
||||
const KanbanView = require("web.KanbanView");
|
||||
const KanbanQuickCreate = require("web.kanban_column_quick_create");
|
||||
|
||||
const _t = core._t;
|
||||
const qweb = core.qweb;
|
||||
|
||||
KanbanQuickCreate.include({
|
||||
init() {
|
||||
this._super.apply(this, arguments);
|
||||
this.isMobile = config.device.isMobile;
|
||||
},
|
||||
/**
|
||||
* KanbanRenderer will decide can we close quick create or not
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_cancel: function () {
|
||||
if (config.device.isMobile) {
|
||||
this.trigger_up("close_quick_create");
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Clear input when showed
|
||||
* @override
|
||||
*/
|
||||
toggleFold: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (config.device.isMobile && !this.folded) {
|
||||
this.$input.val("");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
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 (config.device.isMobile) {
|
||||
if (
|
||||
this._scrollPosition &&
|
||||
this.state.groupedBy.length &&
|
||||
this.widgets.length
|
||||
) {
|
||||
const $column = this.widgets[this.activeColumnIndex].$el;
|
||||
$column.scrollLeft(this._scrollPosition.left);
|
||||
$column.scrollTop(this._scrollPosition.top);
|
||||
}
|
||||
this._computeTabPosition();
|
||||
}
|
||||
this._super.apply(this, arguments);
|
||||
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
|
||||
this.widgets = [];
|
||||
this.columnOptions.recordsDraggable =
|
||||
!config.device.isMobile &&
|
||||
this.columnOptions.originRecordsDraggable;
|
||||
this._renderView();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 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) {
|
||||
const $column = this.widgets[this.activeColumnIndex].$el;
|
||||
this._scrollPosition = {
|
||||
left: $column.scrollLeft(),
|
||||
top: $column.scrollTop(),
|
||||
};
|
||||
} else {
|
||||
this._scrollPosition = null;
|
||||
}
|
||||
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
|
||||
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 (config.device.isMobile) {
|
||||
if (
|
||||
this._canCreateColumn() &&
|
||||
this.quickCreate &&
|
||||
!this.quickCreate.folded
|
||||
) {
|
||||
this._onMobileQuickCreateClicked();
|
||||
}
|
||||
return this.widgets[this.activeColumnIndex].addQuickCreate();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrides to restore the left property and the scrollTop on the updated
|
||||
* column, and to enable the swipe handlers
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
updateColumn: function (localID) {
|
||||
if (config.device.isMobile) {
|
||||
const index = _.findIndex(this.widgets, {db_id: localID});
|
||||
const $column = this.widgets[index].$el;
|
||||
const 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())
|
||||
);
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Private
|
||||
// --------------------------------------------------------------------------
|
||||
/**
|
||||
* Avoid drag'n'drop of kanban records on mobile and let the way to swipe
|
||||
* @private
|
||||
*/
|
||||
_setState: function () {
|
||||
const res = this._super.apply(this, arguments);
|
||||
this.columnOptions.originRecordsDraggable =
|
||||
this.columnOptions.recordsDraggable;
|
||||
this.columnOptions.recordsDraggable =
|
||||
!config.device.isMobile && this.columnOptions.recordsDraggable;
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
* 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) {
|
||||
const column = this.widgets[this.activeColumnIndex];
|
||||
if (!column) {
|
||||
return;
|
||||
}
|
||||
const 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) {
|
||||
const lastItemIndex = this.widgets.length - 1;
|
||||
const moveToIndex = this.activeColumnIndex;
|
||||
let scrollToLeft = 0;
|
||||
for (let i = 0; i < moveToIndex; i++) {
|
||||
const columnWidth = this._getTabWidth(this.widgets[i]);
|
||||
// Apply
|
||||
if (moveToIndex !== lastItemIndex && i === moveToIndex - 1) {
|
||||
const 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) {
|
||||
// Use to compute the sum of the width of all tab
|
||||
const widthChilds = this.widgets.reduce((total, column) => {
|
||||
return total + this._getTabWidth(column);
|
||||
}, 0);
|
||||
// Apply a space around between child if the parent length is higher then the sum of the child width
|
||||
const $tabs = this.$(".o_kanban_mobile_tabs");
|
||||
$tabs.toggleClass(
|
||||
"justify-content-between",
|
||||
$tabs.outerWidth() >= widthChilds
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables swipe event on the current column
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_enableSwipe: function () {
|
||||
const step = _t.database.parameters.direction === "rtl" ? -1 : 1;
|
||||
this.$el.swipe({
|
||||
excludedElements: ".o_kanban_mobile_tabs",
|
||||
swipeLeft: () => {
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
const moveToIndex = this.activeColumnIndex + step;
|
||||
if (moveToIndex < this.widgets.length) {
|
||||
this._moveToGroup(moveToIndex, this.ANIMATE);
|
||||
}
|
||||
},
|
||||
swipeRight: () => {
|
||||
if (!config.device.isMobile) {
|
||||
return;
|
||||
}
|
||||
const moveToIndex = this.activeColumnIndex - step;
|
||||
if (moveToIndex > -1) {
|
||||
this._moveToGroup(moveToIndex, this.ANIMATE);
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve the outerWidth of a given widget column
|
||||
*
|
||||
* @param {KanbanColumn} column
|
||||
* @returns {integer} outerWidth of the found column
|
||||
* @private
|
||||
*/
|
||||
_getTabWidth: function (column) {
|
||||
const 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) {
|
||||
if (this.widgets.length === 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (moveToIndex >= 0 && moveToIndex < this.widgets.length) {
|
||||
this.activeColumnIndex = moveToIndex;
|
||||
}
|
||||
const column = this.widgets[this.activeColumnIndex];
|
||||
this._enableSwipe();
|
||||
if (!column.data.isOpen) {
|
||||
this.trigger_up("column_toggle_fold", {
|
||||
db_id: column.db_id,
|
||||
onSuccess: () => this._layoutUpdate(animate),
|
||||
});
|
||||
} else {
|
||||
this._layoutUpdate(animate);
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderExampleBackground: function () {
|
||||
// Override to avoid display of example background
|
||||
if (!config.device.isMobile) {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderGrouped: function (fragment) {
|
||||
if (config.device.isMobile) {
|
||||
const newFragment = document.createDocumentFragment();
|
||||
this._super.apply(this, [newFragment]);
|
||||
this.defs.push(
|
||||
Promise.all(this.defs).then(() => {
|
||||
const data = [];
|
||||
_.each(this.state.data, function (group) {
|
||||
if (!group.value) {
|
||||
group = _.extend({}, group, {value: _t("Undefined")});
|
||||
data.unshift(group);
|
||||
} else {
|
||||
data.push(group);
|
||||
}
|
||||
});
|
||||
|
||||
const kanbanColumnContainer = document.createElement("div");
|
||||
kanbanColumnContainer.classList.add("o_kanban_columns_content");
|
||||
kanbanColumnContainer.appendChild(newFragment);
|
||||
fragment.appendChild(kanbanColumnContainer);
|
||||
$(
|
||||
qweb.render("KanbanView.MobileTabs", {
|
||||
data: data,
|
||||
quickCreateEnabled: this._canCreateColumn(),
|
||||
})
|
||||
).prependTo(fragment);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_renderView: function () {
|
||||
const def = this._super.apply(this, arguments);
|
||||
if (!config.device.isMobile) {
|
||||
return def;
|
||||
}
|
||||
return def.then(() => {
|
||||
if (this.state.groupedBy.length) {
|
||||
// Force first column for kanban view, because the groupedBy can be changed
|
||||
return this._moveToGroup(0);
|
||||
}
|
||||
if (this._canCreateColumn()) {
|
||||
this._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.quickCreate.toggleFold();
|
||||
this.$(".o_kanban_group").toggle(this.quickCreate.folded);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
_onMobileTabClicked: function (event) {
|
||||
if (this._canCreateColumn() && !this.quickCreate.folded) {
|
||||
this.quickCreate.toggleFold();
|
||||
}
|
||||
this._moveToGroup($(event.currentTarget).index(), true);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_onCloseQuickCreate: function () {
|
||||
if (this.widgets.length && this.quickCreate && !this.quickCreate.folded) {
|
||||
this.$(".o_kanban_group").toggle(true);
|
||||
this.quickCreate.toggleFold();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -1,19 +1,9 @@
|
||||
/* 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 (require) {
|
||||
odoo.define("web_responsive", function () {
|
||||
"use strict";
|
||||
|
||||
const config = require("web.config");
|
||||
const core = require("web.core");
|
||||
const FormRenderer = require("web.FormRenderer");
|
||||
const RelationalFields = require("web.relational_fields");
|
||||
const ViewDialogs = require("web.view_dialogs");
|
||||
const ListRenderer = require("web.ListRenderer");
|
||||
const CalendarRenderer = require("web.CalendarRenderer");
|
||||
|
||||
const _t = core._t;
|
||||
|
||||
// Fix for iOS Safari to set correct viewport height
|
||||
// https://github.com/Faisal-Manzer/postcss-viewport-height-correction
|
||||
function setViewportProperty(doc) {
|
||||
@@ -29,202 +19,4 @@ odoo.define("web_responsive", function (require) {
|
||||
"resize",
|
||||
_.debounce(setViewportProperty(document.documentElement), 100)
|
||||
);
|
||||
|
||||
RelationalFields.FieldStatus.include({
|
||||
/**
|
||||
* Fold all on mobiles.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setState: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (config.device.isMobile) {
|
||||
_.map(this.status_information, (value) => {
|
||||
value.fold = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Sticky Column Selector
|
||||
ListRenderer.include({
|
||||
_renderView: function () {
|
||||
return this._super.apply(this, arguments).then(() => {
|
||||
const $col_selector = this.$el.find(
|
||||
".o_optional_columns_dropdown_toggle"
|
||||
);
|
||||
if ($col_selector.length !== 0) {
|
||||
const $th = this.$el.find("thead>tr:first>th:last");
|
||||
$col_selector.appendTo($th);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_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!
|
||||
// This prevents the action of sorting the column from being
|
||||
// launched.
|
||||
ev.stopPropagation();
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
// Responsive view "action" buttons
|
||||
FormRenderer.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback: function () {
|
||||
this._super.apply(this, arguments);
|
||||
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
|
||||
this._applyFormSizeClass();
|
||||
this._render();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_detach_callback: function () {
|
||||
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* In mobiles, put all statusbar buttons in a dropdown.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderHeaderButtons: function () {
|
||||
const $buttons = this._super.apply(this, arguments);
|
||||
if (
|
||||
!config.device.isMobile ||
|
||||
$buttons.children("button:not(.o_invisible_modifier)").length <= 2
|
||||
) {
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
// $buttons must be appended by JS because all events are bound
|
||||
const $dropdown = $(
|
||||
core.qweb.render("web_responsive.MenuStatusbarButtons")
|
||||
);
|
||||
$buttons.addClass("dropdown-menu").appendTo($dropdown);
|
||||
return $dropdown;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Directly open popup dialog in mobile for search.
|
||||
*/
|
||||
RelationalFields.FieldMany2One.include({
|
||||
start: function () {
|
||||
var superRes = this._super.apply(this, arguments);
|
||||
if (config.device.isMobile) {
|
||||
this.$input.prop("readonly", true);
|
||||
}
|
||||
return superRes;
|
||||
},
|
||||
// --------------------------------------------------------------------------
|
||||
// Private
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_bindAutoComplete: function () {
|
||||
if (!config.device.isMobile) {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_getSearchCreatePopupOptions: function () {
|
||||
const options = this._super.apply(this, arguments);
|
||||
_.extend(options, {
|
||||
on_clear: () => this.reinitialize(false),
|
||||
});
|
||||
return options;
|
||||
},
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
*/
|
||||
_toggleAutoComplete: function () {
|
||||
if (config.device.isMobile) {
|
||||
this._searchCreatePopup("search");
|
||||
} else {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Support for Clear button in search popup.
|
||||
*/
|
||||
ViewDialogs.SelectCreateDialog.include({
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (config.device.isMobile) {
|
||||
this.viewType = "kanban";
|
||||
}
|
||||
this.on_clear =
|
||||
this.options.on_clear ||
|
||||
function () {
|
||||
return undefined;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_prepareButtons: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (config.device.isMobile && this.options.disable_multiple_selection) {
|
||||
this.__buttons.push({
|
||||
text: _t("Clear"),
|
||||
classes: "btn-secondary o_clear_button",
|
||||
close: true,
|
||||
click: function () {
|
||||
this.on_clear();
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
CalendarRenderer.include({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback: function () {
|
||||
this._super.apply(this, arguments);
|
||||
core.bus.on("UI_CONTEXT:IS_SMALL_CHANGED", this, () => {
|
||||
// Hack to force calendar to reload their options and rerender
|
||||
this.calendar.setOption("locale", moment.locale());
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_detach_callback: function () {
|
||||
core.bus.off("UI_CONTEXT:IS_SMALL_CHANGED", this);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_getFullCalendarOptions: function () {
|
||||
const options = this._super.apply(this, arguments);
|
||||
Object.defineProperty(options.views.dayGridMonth, "columnHeaderFormat", {
|
||||
get() {
|
||||
return config.device.isMobile ? "ddd" : "dddd";
|
||||
},
|
||||
});
|
||||
return options;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
/* Copyright 2021 Sergey Shebanin
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
odoo.define("web_responsive.website_apps_menu", function (require) {
|
||||
"use strict";
|
||||
/*
|
||||
* We can't require anything from website here because `web_responsive` doesn't depend on `website`.
|
||||
* In this case we will try to get WebsiteNavbar class through the PublicRoot registry.
|
||||
* WebsiteNavbar can be unavailable if `website` is not installed. In this case this file do nothing.
|
||||
*/
|
||||
const publicRoot = require("root.widget");
|
||||
const lazyloader = require("web.public.lazyloader");
|
||||
const registry = publicRoot._getRegistry();
|
||||
|
||||
function patchNavbar() {
|
||||
const navbar = registry.get("WebsiteNavbar", false);
|
||||
if (!navbar) return false;
|
||||
navbar.Widget.include({
|
||||
/**
|
||||
* We don't need to load app menus
|
||||
* @override
|
||||
*/
|
||||
async _loadAppMenus() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
/**
|
||||
* We add a spinner for the user to understand the loading
|
||||
* @override
|
||||
*/
|
||||
_onOeApplicationsShow: function () {
|
||||
const icon = $(
|
||||
document.querySelector("#oe_main_menu_navbar a.full > i")
|
||||
);
|
||||
icon.removeClass("fa fa-th-large").append(
|
||||
$("<span/>", {class: "fa fa-spin fa-spinner"})
|
||||
);
|
||||
window.location.href = "/web#home";
|
||||
// Prevent dropdown to be showed
|
||||
return false;
|
||||
},
|
||||
});
|
||||
const menu = $("#oe_applications");
|
||||
menu.addClass("o_responsive_loaded").after(
|
||||
"<span class='o_menu_brand'>" + menu.find("a.full").text() + "</span>"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
// Try to patch navbar. If it is not in registry - make another try after lazyload
|
||||
if (!patchNavbar()) {
|
||||
lazyloader.allScriptsLoaded.then(patchNavbar);
|
||||
}
|
||||
});
|
||||
@@ -1,113 +0,0 @@
|
||||
/* Copyright 2019 Odoo S.A.
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_kanban_view {
|
||||
width: 100%;
|
||||
}
|
||||
.o_kanban_view.o_kanban_grouped {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
&.o_renderer_with_searchpanel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o_kanban_mobile_tabs_container {
|
||||
position: sticky;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: #5e5e5e;
|
||||
|
||||
.o_kanban_mobile_add_column {
|
||||
height: $o-kanban-mobile-tabs-height;
|
||||
padding: 10px;
|
||||
border-left: grey 1px solid;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.o_kanban_mobile_tabs {
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: $o-kanban-mobile-tabs-height;
|
||||
overflow-x: auto;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_kanban_mobile_tab {
|
||||
height: $o-kanban-mobile-tabs-height;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
|
||||
&.o_current {
|
||||
font-weight: bold;
|
||||
border-bottom: 3px solid $o-brand-primary;
|
||||
background-color: gray("600");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.o_column_title {
|
||||
white-space: nowrap;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_kanban_columns_content {
|
||||
position: relative;
|
||||
}
|
||||
// [class] to get same specificity as elsewhere (kanban_view.less)
|
||||
&[class] .o_kanban_group:not(.o_column_folded) {
|
||||
@include o-position-absolute(
|
||||
$top: $o-kanban-mobile-tabs-height,
|
||||
$left: 0,
|
||||
$bottom: 0
|
||||
);
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-left: 0; // override the margin-left: -1px of the desktop mode
|
||||
border: none;
|
||||
|
||||
&.o_current {
|
||||
position: inherit;
|
||||
top: 0;
|
||||
|
||||
&.o_kanban_no_records {
|
||||
// set a default height for clarity when embedded in another view
|
||||
min-height: $o-kanban-mobile-empty-height;
|
||||
}
|
||||
}
|
||||
|
||||
.o_kanban_header {
|
||||
display: none;
|
||||
}
|
||||
.o_kanban_record,
|
||||
.o_kanban_quick_create {
|
||||
border: none;
|
||||
border-bottom: 1px solid lightgray;
|
||||
padding: 10px 16px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.modal {
|
||||
z-index: 1052;
|
||||
}
|
||||
.o_kanban_view .o_column_quick_create {
|
||||
.o_quick_create_folded {
|
||||
display: none !important;
|
||||
}
|
||||
.o_quick_create_unfolded {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
/* 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). */
|
||||
|
||||
$chatter_zone_width: 35%;
|
||||
$chatter_zone_width: 35% !important;
|
||||
|
||||
// Support for long titles
|
||||
@include media-breakpoint-up(md) {
|
||||
@@ -19,10 +20,9 @@ html .o_web_client .o_action_manager .o_action {
|
||||
overflow: auto;
|
||||
|
||||
.o_content {
|
||||
overflow: visible;
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -78,43 +78,16 @@ html .o_web_client .o_action_manager .o_action {
|
||||
box-shadow: inset 0 -5px #7c7bad;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Size of labels
|
||||
.o_web_client {
|
||||
&.o_chatter_position_sided {
|
||||
.o_action_manager {
|
||||
.o_content,
|
||||
.modal-content {
|
||||
@include media-breakpoint-up(xl, $o-extra-grid-breakpoints) {
|
||||
.o_inner_group {
|
||||
.o_td_label {
|
||||
min-width: 260px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-between(lg, xl, $o-extra-grid-breakpoints) {
|
||||
.o_group_col_6 {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.settings > .app_settings_block .o_settings_container {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
&:not(.o_chatter_position_sided) {
|
||||
@include media-breakpoint-up(lg, $o-extra-grid-breakpoints) {
|
||||
.o_action_manager {
|
||||
.o_content,
|
||||
.modal-content {
|
||||
.o_inner_group {
|
||||
.o_td_label {
|
||||
min-width: 260px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_kanban_view .o_control_panel .o_cp_bottom_right .o_cp_pager .btn-group {
|
||||
top: -1px;
|
||||
}
|
||||
.o_kanban_renderer {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,13 +97,13 @@ html .o_web_client .o_action_manager .o_action {
|
||||
max-width: 100%;
|
||||
|
||||
// Form views
|
||||
.o_form_view {
|
||||
.o_form_editable {
|
||||
.o_form_sheet {
|
||||
max-width: calc(100% - 32px);
|
||||
max-width: calc(100% - 32px) !important;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.o_td_label .o_form_label:not(.o_status):not(.o_calendar_invitation) {
|
||||
.o_cell .o_form_label:not(.o_status):not(.o_calendar_invitation) {
|
||||
min-height: 23px;
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-bottom: 10px;
|
||||
@@ -139,10 +112,14 @@ html .o_web_client .o_action_manager .o_action {
|
||||
.o_horizontal_separator {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
// Some UX improvements for form in edit mode
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_field_widget {
|
||||
vertical-align: middle;
|
||||
.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) {
|
||||
@@ -153,13 +130,13 @@ html .o_web_client .o_action_manager .o_action {
|
||||
}
|
||||
&.o_field_float_percentage,
|
||||
&.o_field_monetary,
|
||||
&.o_field_many2manytags,
|
||||
&.o_field_many2many_selection,
|
||||
.o_field_many2one_selection {
|
||||
align-items: center;
|
||||
}
|
||||
.o_field_many2one_selection .o_input_dropdown,
|
||||
&.o_datepicker,
|
||||
&.o_field_partner_autocomplete {
|
||||
&.o_partner_autocomplete_info {
|
||||
input {
|
||||
min-height: 35px;
|
||||
}
|
||||
@@ -173,53 +150,34 @@ html .o_web_client .o_action_manager .o_action {
|
||||
right: 6px;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_FormRenderer_chatterContainer {
|
||||
padding-top: 0;
|
||||
.o_Activity_info {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.o_ActivityBox_title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.o_MessageList_separatorDate {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
// Sided chatter scrolling behavior
|
||||
.o_Chatter {
|
||||
height: fit-content;
|
||||
.o_Chatter_fixedPanel {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: white;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.o_Chatter_scrollPanel {
|
||||
overflow: initial;
|
||||
.o_field_many2many_selection .o_dropdown_button {
|
||||
top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky statusbar
|
||||
|
||||
.o_form_statusbar {
|
||||
position: sticky;
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Support for long title (with ellipsis)
|
||||
.oe_title {
|
||||
span.o_field_widget:not(.oe_inline) {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: initial;
|
||||
&:active {
|
||||
white-space: normal;
|
||||
.o_field_widget:not(.oe_inline) {
|
||||
display: block;
|
||||
span {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: initial;
|
||||
&:active {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,16 +189,11 @@ html .o_web_client .o_action_manager .o_action {
|
||||
.oe_button_box {
|
||||
.o_dropdown_more {
|
||||
button:last-child {
|
||||
border-right: 1px solid gray("400");
|
||||
border-right: 1px solid $gray-400 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid overflow on forms with title and/or button box
|
||||
.oe_title {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oe_button_box + .oe_title,
|
||||
.oe_button_box + .oe_avatar + .oe_title {
|
||||
width: 100%;
|
||||
@@ -257,53 +210,11 @@ html .o_web_client .o_action_manager .o_action {
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
// Make all input groups vertical
|
||||
.o_group_col_6,
|
||||
.o_group_col_8 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Statusbar buttons dropdown for mobiles
|
||||
.o_statusbar_buttons_dropdown {
|
||||
border: {
|
||||
bottom: 0;
|
||||
radius: 0;
|
||||
top: 0;
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
.o_statusbar_buttons.dropdown-menu {
|
||||
.btn {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 0.2rem;
|
||||
white-space: nowrap;
|
||||
@include media-breakpoint-down(xs) {
|
||||
max-width: 80vw;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_statusbar_status {
|
||||
// Arrow from rightmost button exceeds allowed width
|
||||
.o_arrow_button:first-child::before {
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Full width in form sheets
|
||||
.o_form_sheet,
|
||||
.o_FormRenderer_chatterContainer {
|
||||
min-width: auto;
|
||||
max-width: 98%;
|
||||
max-width: 98% !important;
|
||||
}
|
||||
|
||||
// Settings pages
|
||||
@@ -320,7 +231,7 @@ html .o_web_client .o_action_manager .o_action {
|
||||
.o_Chatter_composer {
|
||||
&.o-has-current-partner-avatar {
|
||||
grid-template-columns: 0px 1fr;
|
||||
padding: 1rem 1rem 1.5rem 1rem;
|
||||
padding: 1rem 1rem 1.5rem 1rem !important;
|
||||
}
|
||||
|
||||
.o_Composer_sidebarMain {
|
||||
@@ -343,71 +254,26 @@ html .o_web_client .o_action_manager .o_action {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Sided chatter, if user wants
|
||||
.o_chatter_position_sided & {
|
||||
@include media-breakpoint-up(lg) {
|
||||
.o_form_view:not(.o_form_nosheet) {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
height: 100%;
|
||||
|
||||
.o_form_sheet_bg {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
> .o_form_sheet {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.o_FormRenderer_chatterContainer {
|
||||
border-left: 1px solid gray("400");
|
||||
flex: 0 0 $chatter_zone_width;
|
||||
max-width: initial;
|
||||
min-width: initial;
|
||||
overflow: auto;
|
||||
|
||||
.o_chatter_header_container {
|
||||
padding-top: $grid-gutter-width * 0.5;
|
||||
top: 0;
|
||||
position: sticky;
|
||||
background-color: $o-view-background-color;
|
||||
z-index: 1;
|
||||
|
||||
// Scrollable input text to avoid hide conversation & buttons
|
||||
.o_composer_text_field {
|
||||
max-height: 120px;
|
||||
overflow-y: auto !important; /* Forced because Odoo uses inline style */
|
||||
}
|
||||
.o_attachments_list {
|
||||
overflow: auto;
|
||||
max-height: $o-mail-attachment-image-size * 3;
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
.o_attachments_previews {
|
||||
overflow: auto;
|
||||
max-height: $o-mail-attachment-image-size * 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
z-index: 999;
|
||||
}
|
||||
thead tr:nth-child(1) th {
|
||||
background-color: $o-list-footer-bg-color;
|
||||
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 {
|
||||
@@ -416,6 +282,15 @@ html .o_web_client .o_action_manager .o_action {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -423,14 +298,39 @@ html .o_web_client .o_action_manager .o_action {
|
||||
|
||||
// Big checkboxes
|
||||
.o_list_view,
|
||||
.o_settings_container .o_setting_box {
|
||||
.o_setting_container .o_setting_box {
|
||||
.o_setting_right_pane {
|
||||
margin-left: 34px;
|
||||
}
|
||||
.custom-checkbox:not(.o_boolean_toggle) {
|
||||
.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-checkbox {
|
||||
margin-top: 0;
|
||||
}
|
||||
.form-check-input {
|
||||
height: 1em !important;
|
||||
width: 1em !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
.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;
|
||||
@@ -444,18 +344,41 @@ html .o_web_client .o_action_manager .o_action {
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_list_view {
|
||||
.custom-checkbox:not(.o_boolean_toggle) {
|
||||
top: -6px;
|
||||
|
||||
.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-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;
|
||||
min-width: initial;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_base_settings
|
||||
.o_setting_container
|
||||
.settings
|
||||
> .app_settings_block
|
||||
.o_settings_container {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
body:not(.o_statusbar_buttons) {
|
||||
.oe-toolbar {
|
||||
z-index: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_inner_group > .mb-sm-0 {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.w-26 {
|
||||
width: 26%;
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/* Copyright 2021 ITerra - Sergey Shebanin
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
// Website main navbar and his AppsMenu button
|
||||
#oe_main_menu_navbar {
|
||||
#oe_applications .dropdown-toggle::after {
|
||||
display: none;
|
||||
}
|
||||
a.full {
|
||||
> i {
|
||||
padding: 0 10px 0 0;
|
||||
font-size: 17px;
|
||||
}
|
||||
font-size: 20px;
|
||||
line-height: 46px;
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 46px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
#oe_applications.o_responsive_loaded {
|
||||
a.full {
|
||||
width: 46px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.o_menu_brand {
|
||||
white-space: nowrap;
|
||||
padding: 0 16px 0 4px;
|
||||
text-transform: capitalize;
|
||||
@include media-breakpoint-down(sm) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,108 +4,165 @@
|
||||
Copyright 2018 Alexandre Díaz
|
||||
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.html).
|
||||
-->
|
||||
<templates id="form_view" xml:space="preserve">
|
||||
<!-- Template for buttons that display only the icon in xs -->
|
||||
<t t-name="web_responsive.icon_button">
|
||||
<t t-name="web_responsive.icon_button" owl="1">
|
||||
<i t-attf-class="fa fa-#{icon}" t-att-title="label" />
|
||||
<span class="d-none d-sm-inline" t-esc="label" />
|
||||
</t>
|
||||
<t t-name="web_responsive.MenuStatusbarButtons">
|
||||
<div class="dropdown">
|
||||
<t
|
||||
t-name="web.ResponsiveFormView.Buttons"
|
||||
t-inherit="web.FormView.Buttons"
|
||||
owl="1"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_form_button_cancel')]"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="data-hotkey">d</attribute>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_form_button_create')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
class="o_statusbar_buttons_dropdown btn btn-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
class="btn btn-secondary o_form_button_create"
|
||||
data-hotkey="c"
|
||||
t-on-click.stop="create"
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'cogs'" />
|
||||
<t t-set="label">Quick actions</t>
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label"> Create</t>
|
||||
</t>
|
||||
</button>
|
||||
<!-- A div.o_statusbar_buttons.dropdown-menu
|
||||
is appended here from JS -->
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-extend="FormView.buttons">
|
||||
<!-- Change "Edit" button hotkey to "E" -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="attributes">
|
||||
<attribute name="accesskey">e</attribute>
|
||||
</t>
|
||||
|
||||
<t
|
||||
t-name="web.ResponsiveFormView"
|
||||
t-inherit="web.FormView"
|
||||
owl="1"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_form_button_create')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary o_form_button_create"
|
||||
data-hotkey="c"
|
||||
t-on-click.stop="create"
|
||||
t-att-disabled="state.isDisabled"
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label"> Create</t>
|
||||
</t>
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t
|
||||
t-name="web.ResponsiveFormStatusIndicator"
|
||||
t-inherit="web.FormStatusIndicator"
|
||||
owl="1"
|
||||
>
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'pencil'" />
|
||||
<t t-set="label">Edit</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".o_form_button_create" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".o_form_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'" />
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'" />
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_form_button_cancel')]"
|
||||
position="attributes"
|
||||
>
|
||||
<attribute name="data-hotkey">d</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-extend="KanbanView.buttons">
|
||||
<t
|
||||
t-name="web.ResponsiveKanbanView.Buttons"
|
||||
t-inherit="web.KanbanView.Buttons"
|
||||
owl="1"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery="button" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label" t-value="create_text || _t('Create')" />
|
||||
</t>
|
||||
</t>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o-kanban-button-new')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary o-kanban-button-new"
|
||||
accesskey="c"
|
||||
t-on-click="() => this.createRecord(null)"
|
||||
data-bounce-button=""
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label"> Create</t>
|
||||
</t>
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
<t t-extend="ListView.buttons">
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_list_button_discard" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
<t
|
||||
t-name="web.ResponsiveListView.Buttons"
|
||||
t-inherit="web.ListView.Buttons"
|
||||
owl="1"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_list_button_add" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".o_list_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'" />
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-jquery=".o_list_button_discard" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'" />
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
<t t-extend="CalendarView.navigation_buttons">
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_calendar_button_today" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'calendar-check-o'" />
|
||||
<t t-set="label">Today</t>
|
||||
</t>
|
||||
</t>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_list_button_add')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary o_list_button_add"
|
||||
data-hotkey="c"
|
||||
t-on-click="onClickCreate"
|
||||
data-bounce-button=""
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'" />
|
||||
<t t-set="label"> Create</t>
|
||||
</t>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_list_button_save')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary o_list_button_save"
|
||||
data-hotkey="s"
|
||||
t-on-click.stop="onClickSave"
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'" />
|
||||
<t t-set="label"> Save</t>
|
||||
</t>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//button[contains(@class, 'o_list_button_discard')]"
|
||||
position="replace"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary o_list_button_discard"
|
||||
data-hotkey="d"
|
||||
t-on-click="onClickDiscard"
|
||||
t-on-mousedown="onMouseDownDiscard"
|
||||
>
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'" />
|
||||
<t t-set="label"> Discard</t>
|
||||
</t>
|
||||
</button>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
Reference in New Issue
Block a user