[MIG] web_responsive: Migration to 15.0

This commit is contained in:
Sergey Shebanin
2021-10-21 17:40:06 +03:00
committed by anjeel.haria
parent cfea8e7762
commit cd321a6ef1
40 changed files with 2229 additions and 2415 deletions

View File

@@ -0,0 +1,116 @@
/* 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.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%;
}
}
}
.o_kanban_view.o_kanban_ungrouped {
.o_kanban_record {
padding-left: 16px !important;
padding-right: 16px !important;
}
}

View File

@@ -0,0 +1,357 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
$chatter_zone_width: 35%;
// 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);
}
}
// 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;
}
}
max-width: 100%;
}
@include media-breakpoint-down(sm) {
.o_calendar_view .o_calendar_widget {
.fc-timeGridDay-view .fc-axis,
.fc-timeGridWeek-view .fc-axis {
padding-left: 0px;
}
.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;
}
}
}
}
// Normal views
.o_content,
.modal-content {
max-width: 100%;
// Form views
.o_form_view {
.o_form_sheet {
max-width: calc(100% - 32px);
overflow-x: 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;
}
}
// Sticky statusbar
.o_form_statusbar {
position: sticky;
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;
}
}
}
@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");
}
}
}
// 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%;
}
// Avoid overflow on modals
.o_form_sheet {
min-width: auto;
}
// Render website inputs properly in phones
.o_group .o_field_widget.o_text_overflow {
// Overrides another !important
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%;
}
// 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;
}
.o_Composer_sidebarMain {
display: none;
}
}
}
}
}
//No content message improvements on mobile
@include media-breakpoint-down(md) {
.o_view_nocontent {
top: 80px;
}
.o_nocontent_help {
box-shadow: none;
}
.o_sample_data_disabled {
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 {
.o_list_table {
// th & td are here for compatibility with chrome
thead tr:nth-child(1) th {
position: sticky;
top: 0;
z-index: 1;
}
thead tr:nth-child(1) th {
background-color: $o-list-footer-bg-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;
}
}
}
}
// Big checkboxes
.o_list_view {
.custom-checkbox:not(.o_boolean_toggle) {
margin-right: 10px;
.custom-control-label {
top: -6px;
&::after {
width: 24px;
height: 24px;
}
&::before {
outline: none !important;
border: 1px solid #4c4c4c;
width: 24px;
height: 24px;
}
}
}
}

View File

@@ -0,0 +1,564 @@
/* 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._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.recordsDraggable = !config.device.isMobile;
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();
}
},
});
});

View File

@@ -0,0 +1,144 @@
/* Copyright 2018 Tecnativa - Jairo Llopis
* Copyright 2021 ITerra - Sergey Shebanin
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
odoo.define("web_responsive", function (require) {
"use strict";
const config = require("web.config");
const core = require("web.core");
const FormRenderer = require("web.FormRenderer");
const RelationalFields = require("web.relational_fields");
const ListRenderer = require("web.ListRenderer");
const CalendarRenderer = require("web.CalendarRenderer");
// 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)
);
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;
},
});
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;
},
});
});

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2017 LasLabs Inc.
Copyright 2018 Alexandre Díaz
Copyright 2018 Tecnativa - Jairo Llopis
Copyright 2021 ITerra - Sergey Shebanin
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">
<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">
<button
class="o_statusbar_buttons_dropdown btn btn-secondary dropdown-toggle"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<t t-call="web_responsive.icon_button">
<t t-set="icon" t-value="'cogs'" />
<t t-set="label">Quick actions</t>
</t>
</button>
<!-- A div.o_statusbar_buttons.dropdown-menu
is appended here from JS -->
</div>
</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>
<!-- 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>
</t>
<t t-extend="KanbanView.buttons">
<!-- 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>
</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>
<!-- 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>
</t>
</templates>