[IMP] web,crm: revamp grouped kanban mobile implementation
This commit revamps the grouped-kanban implementation for smaller screens (aka. mobile) by making it more "responsive" and avoiding mobile-specific variation. It makes the implementation simpler and closer to what the user expects from the desktop version. In a nutshell: - columns are displayed individually ; an horizontal scroll allows to switch to the other ones, "snapping" to the column (aka. carroussel-like). - each column takes 90% of the viewport's width to give a hint of its siblings. - each column scrolls (vertically) individually to avoid being lost when switching from one column to another. - folded columns are "virtually unfolded": they takes the same space as the other ones, but content is loaded on-demand. - some configuration modals are fullscreen for ease of use. Part of the SCSS revamp task-2704984 task-2883057 Part-of: odoo/odoo#94134
This commit is contained in:
@@ -4,8 +4,10 @@
|
||||
<t t-name="KanbanView.ForecastColumnQuickCreate">
|
||||
<div class="o_column_quick_create">
|
||||
<div class="o_quick_create_folded">
|
||||
<span class="o_kanban_add_column"><i class="fa fa-plus" role="img" t-att-aria-label="widget.addColumnLabel" t-att-title="widget.addColumnLabel"/></span>
|
||||
<span class="o_kanban_title"><t t-esc="widget.addColumnLabel"/></span>
|
||||
<button class="o_kanban_add_column btn btn-outline-secondary w-100">
|
||||
<i class="fa fa-plus" role="img" t-att-aria-label="widget.addColumnLabel" t-att-title="widget.addColumnLabel"/>
|
||||
<t t-out="widget.addColumnLabel"/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -2,6 +2,7 @@ odoo.define('web.KanbanColumn', function (require) {
|
||||
"use strict";
|
||||
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var session = require('web.session');
|
||||
var Dialog = require('web.Dialog');
|
||||
var KanbanRecord = require('web.KanbanRecord');
|
||||
@@ -66,7 +67,7 @@ var KanbanColumn = Widget.extend({
|
||||
this.offset = 0;
|
||||
this.loadMoreCount = data.loadMoreCount;
|
||||
this.loadMoreOffset = data.loadMoreOffset;
|
||||
this.canBeFolded = this.folded;
|
||||
this.isMobile = config.device.isMobile;
|
||||
|
||||
if (options.hasProgressBar) {
|
||||
this.barOptions = {
|
||||
@@ -107,7 +108,7 @@ var KanbanColumn = Widget.extend({
|
||||
defs.push(this._addRecord(this.data_records[i]));
|
||||
}
|
||||
|
||||
if (this.recordsDraggable) {
|
||||
if (this.recordsDraggable && !config.device.isMobile) {
|
||||
this.$el.sortable({
|
||||
connectWith: '.o_kanban_group',
|
||||
containment: this.draggable ? false : 'parent',
|
||||
@@ -147,21 +148,23 @@ var KanbanColumn = Widget.extend({
|
||||
}
|
||||
});
|
||||
}
|
||||
this.$el.click(function (event) {
|
||||
if (self.folded) {
|
||||
self._onToggleFold(event);
|
||||
}
|
||||
});
|
||||
if (!config.device.isMobile) {
|
||||
this.$el.click(function (event) {
|
||||
if (self.folded) {
|
||||
self._onToggleFold(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (this.barOptions) {
|
||||
this.$el.addClass('o_kanban_has_progressbar');
|
||||
this.progressBar = this._getKanbanColumnProgressBar();
|
||||
defs.push(this.progressBar.appendTo(this.$header));
|
||||
}
|
||||
|
||||
var title = this.folded ? this.title + ' (' + this.data.count + ')' : this.title;
|
||||
var title = !config.device.isMobile && this.folded ? this.title + ' (' + this.data.count + ')' : this.title;
|
||||
this.$header.find('.o_column_title').text(title);
|
||||
|
||||
this.$el.toggleClass('o_column_folded', this.canBeFolded);
|
||||
this.$el.toggleClass('o_column_folded', !config.device.isMobile && this.folded);
|
||||
if (this.tooltipInfo) {
|
||||
this.$header.find('.o_kanban_header_title').tooltip({}).attr('data-original-title', this.tooltipInfo);
|
||||
}
|
||||
@@ -365,7 +368,11 @@ var KanbanColumn = Widget.extend({
|
||||
*/
|
||||
_onLoadMore: function (event) {
|
||||
event.preventDefault();
|
||||
this.trigger_up('kanban_load_column_records', { loadMoreOffset: this.loadMoreOffset });
|
||||
if (this.folded) {
|
||||
this.trigger_up('column_toggle_fold');
|
||||
} else {
|
||||
this.trigger_up('kanban_load_column_records', { loadMoreOffset: this.loadMoreOffset });
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -7,6 +7,7 @@ odoo.define('web.kanban_column_quick_create', function (require) {
|
||||
*/
|
||||
|
||||
var core = require('web.core');
|
||||
var config = require('web.config');
|
||||
var Dialog = require('web.Dialog');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
@@ -35,7 +36,7 @@ var ColumnQuickCreate = Widget.extend({
|
||||
this.applyExamplesText = options.applyExamplesText || _t("Use This For My Kanban");
|
||||
this.examples = options.examples;
|
||||
this.folded = true;
|
||||
this.isMobile = false;
|
||||
this.isMobile = config.device.isMobile;
|
||||
this.isFirstColumn = options.isFirstColumn;
|
||||
},
|
||||
/**
|
||||
@@ -204,6 +205,7 @@ var ColumnQuickCreate = Widget.extend({
|
||||
text: _t('Close'),
|
||||
}],
|
||||
size: "large",
|
||||
fullscreen: config.device.isMobile,
|
||||
title: _t("Kanban Examples"),
|
||||
}).open();
|
||||
dialog.on('closed', this, function () {
|
||||
|
||||
@@ -9,6 +9,7 @@ odoo.define('web.KanbanController', function (require) {
|
||||
|
||||
var BasicController = require('web.BasicController');
|
||||
var Context = require('web.Context');
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var Dialog = require('web.Dialog');
|
||||
var Domain = require('web.Domain');
|
||||
@@ -50,6 +51,14 @@ var KanbanController = BasicController.extend({
|
||||
this.hasButtons = params.hasButtons;
|
||||
this.quickCreateEnabled = params.quickCreateEnabled;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async start() {
|
||||
await this._super(...arguments);
|
||||
const { groupedBy } = this.model.get(this.handle);
|
||||
this.el.classList.toggle("o_action_delegate_scroll", config.device.isMobile && groupedBy.length);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
@@ -72,6 +81,14 @@ var KanbanController = BasicController.extend({
|
||||
this.$buttons.appendTo($node);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async update() {
|
||||
await this._super(...arguments);
|
||||
const { groupedBy } = this.model.get(this.handle);
|
||||
this.el.classList.toggle("o_action_delegate_scroll", config.device.isMobile && groupedBy.length);
|
||||
},
|
||||
/**
|
||||
* In grouped mode, set 'Create' button as btn-secondary if there is no column
|
||||
* (except if we can't create new columns)
|
||||
|
||||
@@ -428,6 +428,13 @@
|
||||
padding: 0;
|
||||
background-color: $gray-100;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
min-height: initial;
|
||||
height: 100%;
|
||||
overflow: scroll hidden !important;
|
||||
scroll-snap-type: x mandatory;
|
||||
}
|
||||
|
||||
.o_kanban_record, .o_kanban_quick_create {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
@@ -441,7 +448,7 @@
|
||||
|
||||
// Kanban Grouped Layout - Column default
|
||||
.o_kanban_group {
|
||||
flex: 0 0 auto;
|
||||
flex: 0 0 $o-kanban-default-record-width + 2*$o-kanban-group-padding;
|
||||
padding: 0 $o-kanban-group-padding $o-kanban-group-padding $o-kanban-group-padding;
|
||||
background-color: inherit;
|
||||
|
||||
@@ -471,22 +478,16 @@
|
||||
.o_kanban_config {
|
||||
visibility: hidden;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
i {
|
||||
@include o-kanban-icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_kanban_load_more {
|
||||
padding: $o-kanban-record-margin 0;
|
||||
box-shadow: inset 0 10px 13px -13px black;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:not(.o_column_folded) {
|
||||
width: $o-kanban-default-record-width + 2*$o-kanban-group-padding;
|
||||
}
|
||||
|
||||
&.o_kanban_dragged {
|
||||
background-color: $o-main-text-color;
|
||||
|
||||
@@ -495,45 +496,50 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.ui-sortable .o_kanban_header_title .o_column_title {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
// Kanban Grouped Layout - Column Folded
|
||||
.o_kanban_group.o_column_folded {
|
||||
@include o-kanban-slim-col;
|
||||
background-color: $gray-200;
|
||||
// don't visually fold on smaller screens (aka. mobile)
|
||||
@include media-breakpoint-up(md) {
|
||||
@include o-kanban-slim-col;
|
||||
background-color: $gray-200;
|
||||
|
||||
& + .o_kanban_group.o_column_folded {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
.o_kanban_header_title {
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
|
||||
.o_column_title {
|
||||
@include o-kanban-v-title;
|
||||
& + .o_kanban_group.o_column_folded {
|
||||
margin-left: 1px;
|
||||
}
|
||||
.o_column_unfold {
|
||||
@include o-kanban-icon(1);
|
||||
}
|
||||
}
|
||||
|
||||
> .o_kanban_record, .o_kanban_quick_add, .o_kanban_config, .o_kanban_load_more {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
&:hover, &.o_kanban_hover {
|
||||
.o_kanban_header_title {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
|
||||
.o_column_title {
|
||||
@include o-kanban-v-title;
|
||||
}
|
||||
|
||||
.o_column_unfold {
|
||||
@include o-kanban-icon(1);
|
||||
}
|
||||
}
|
||||
|
||||
> .o_kanban_record, .o_kanban_quick_add, .o_kanban_config, .o_kanban_load_more {
|
||||
display: none !important;
|
||||
}
|
||||
&:hover, &.o_kanban_hover {
|
||||
.o_kanban_header_title {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kanban Grouped Layout - "Create new column" column
|
||||
.o_column_quick_create {
|
||||
flex-basis: $o-kanban-small-record-width + 2*$o-kanban-group-padding;
|
||||
|
||||
.o_quick_create_folded {
|
||||
cursor: pointer;
|
||||
padding: 12px 16px;
|
||||
@@ -541,21 +547,15 @@
|
||||
font-weight: $font-weight-bold;
|
||||
@include o-hover-opacity(.7);
|
||||
@include o-hover-text-color($text-muted, $body-color);
|
||||
|
||||
.o_kanban_add_column {
|
||||
margin-right: $o-kanban-inside-hgutter;
|
||||
display: inline-block;
|
||||
padding: 10px 14px;
|
||||
background-color: rgba($text-muted, .1);
|
||||
}
|
||||
}
|
||||
|
||||
.o_quick_create_unfolded {
|
||||
margin: $border-width ($o-kanban-inside-hgutter * .5) 0;
|
||||
padding: $o-kanban-inside-vgutter $o-kanban-inside-hgutter;
|
||||
width: $o-kanban-small-record-width;
|
||||
height: 100%;
|
||||
background-color: $o-view-background-color;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.o_kanban_header {
|
||||
height: 50px;
|
||||
@@ -579,6 +579,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_kanban_group, .o_column_quick_create {
|
||||
flex: 1 0 90%; // don't take full width to give a hint of next/previous column
|
||||
scroll-snap-align: center;
|
||||
overflow-y: scroll;
|
||||
|
||||
.o_kanban_header {
|
||||
position: sticky; // keep it visible
|
||||
top: 0;
|
||||
z-index: 10; // same as the sticky ControlPanel
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.o_kanban_header_title {
|
||||
margin-top: 0 !important; // avoid sticky glitch when scrolling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kanban UN-grouped Layout
|
||||
&.o_kanban_ungrouped {
|
||||
min-height: 100%;
|
||||
|
||||
@@ -14,6 +14,14 @@
|
||||
.modal-body {
|
||||
&.o_act_window {
|
||||
padding: 0;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_action {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_modal_header {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="o_kanban_config dropdown">
|
||||
<a class="dropdown-toggle o-no-caret" data-toggle="dropdown" href="#"><i class="fa fa-gear" role="img" aria-label="Settings" title="Settings"/></a>
|
||||
<div class="dropdown-menu o_cursor_default" role="menu">
|
||||
<a role="menuitem" class="dropdown-item o_kanban_toggle_fold" href="#">Fold</a>
|
||||
<a t-if="!widget.isMobile" role="menuitem" class="dropdown-item o_kanban_toggle_fold" href="#">Fold</a>
|
||||
<t t-if="widget.grouped_by_m2o">
|
||||
<a t-if="widget.editable and widget.id" role="menuitem" class="dropdown-item o_column_edit" href="#">Edit Stage</a>
|
||||
<a t-if="widget.deletable and widget.id" role="menuitem" class="dropdown-item o_column_delete" href="#">Delete</a>
|
||||
@@ -39,14 +39,16 @@
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.LoadMore">
|
||||
<a href="#">Load more... (<t t-esc="widget.loadMoreCount"/> remaining)</a>
|
||||
<button class="btn btn-outline-primary w-100 mt-4">Load more... (<t t-out="widget.loadMoreCount"/> remaining)</button>
|
||||
</t>
|
||||
|
||||
<t t-name="KanbanView.ColumnQuickCreate">
|
||||
<div class="o_column_quick_create">
|
||||
<div class="o_quick_create_folded">
|
||||
<span class="o_kanban_add_column"><i class="fa fa-plus" role="img" aria-label="Add column" title="Add column"/></span>
|
||||
<span class="o_kanban_title">Add a Column</span>
|
||||
<button class="o_kanban_add_column btn btn-outline-secondary w-100">
|
||||
<i class="fa fa-plus" role="img" aria-label="Add column" title="Add column"/>
|
||||
Add a Column
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_quick_create_unfolded">
|
||||
<div class="o_kanban_header">
|
||||
|
||||
@@ -43,7 +43,8 @@ html {
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
// Made the o_action scroll instead of its o_content.
|
||||
& {
|
||||
// Except when the view wants to handle the scroll itself.
|
||||
&:not(.o_action_delegate_scroll) {
|
||||
overflow: auto;
|
||||
|
||||
> .o_content {
|
||||
|
||||
Reference in New Issue
Block a user