[IMP] mail: topChatter, review design

task-3005130

Part-of: odoo/odoo#102528
This commit is contained in:
stefanorigano (SRI)
2022-10-07 14:37:01 +02:00
committed by Alexandre Kühn
parent db86a64047
commit df6373970c
19 changed files with 114 additions and 127 deletions
@@ -1,3 +1,11 @@
// ------------------------------------------------------------------
// Layout
// ------------------------------------------------------------------
.o_ActivityBox_activityList {
max-width: var(--Chatter-max-width, none);
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
@@ -0,0 +1,3 @@
.o_AttachmentList {
max-width: var(--Chatter-max-width, none);
}
@@ -18,7 +18,7 @@
<t t-if="chatter.composerView">
<Composer
className="'o_Chatter_composer border-bottom'"
classNameObj="{ 'o-bordered border-start border-end': chatter.hasExternalBorder }"
classNameObj="{ 'o-bordered': chatter.hasExternalBorder }"
record="chatter.composerView"
/>
</t>
@@ -1,3 +1,7 @@
.o_ChatterContainer.o-isInFormSheetBg {
max-width: $o-form-view-sheet-max-width;
.o_ChatterContainer {
width: 100%;
&.o-isInFormSheetBg {
max-width: $o-form-view-sheet-max-width;
}
}
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-name="mail.ChatterContainer" owl="1">
<div class="o_ChatterContainer flex-grow-1 d-flex w-100" t-att-class="{ 'o-isInFormSheetBg mx-auto': props.isInFormSheetBg }" t-attf-class="{{ props.className }}" data-command-category="mail" t-ref="root">
<div class="o_ChatterContainer flex-grow-1 d-flex" t-att-class="{ 'o-isInFormSheetBg mx-auto': props.isInFormSheetBg }" t-attf-class="{{ props.className }}" data-command-category="mail" t-ref="root">
<t t-if="chatter">
<Chatter record="chatter"/>
</t>
@@ -3,34 +3,31 @@
// ------------------------------------------------------------------
.o_ChatterTopbar {
// We use min-height to allow multiples buttons lines on mobile.
min-height: $o-statusbar-height + $border-width;
min-height: $o-statusbar-height;
box-sizing: content-box;
@include media-breakpoint-down(md) {
overflow-x: auto;
&::-webkit-scrollbar {
display: none;
}
}
}
.o_ChatterTopbar_controllers {
padding-left: var(--ChatterTopbar-padding-left, 0);
}
.o_ChatterTopbar_tools {
padding-right: var(--ChatterTopbar-padding-right, 0);
}
.o_ChatterTopbar_button {
margin-bottom: -$border-width; /* Needed to allow "overriding" of the bottom border */
margin-bottom: var(--ChatterTopbar-padding-v, 0);
margin-top: var(--ChatterTopbar-padding-v, 0);
}
.o_ChatterTopbar_buttonClose {
width: $o-statusbar-height;
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_ChatterTopbar_button {
&:hover, &:focus {
background-color: $gray-200;
}
&.o-active {
@include o-hover-text-color($o-brand-odoo, $link-hover-color);
border-radius: $nav-tabs-border-radius $nav-tabs-border-radius 0 0;
background-color: $gray-100;
&:focus {
background-color: $gray-200;
}
}
}
@@ -4,14 +4,14 @@
<t t-name="mail.ChatterTopbar" owl="1">
<t t-if="chatterTopbar">
<div class="o_ChatterTopbar justify-content-between d-flex" t-attf-class="{{ className }}" t-ref="root">
<div class="o_ChatterTopbar_actions flex-wrap-reverse flex-fill d-flex border-bottom" t-attf-class="{{ chatterTopbar.chatter.composerView ? 'o-has-active-button' : 'border-white' }}">
<t t-if="chatterTopbar.chatter.threadView">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage btn btn-link"
<div class="o_ChatterTopbar_actions flex-fill d-flex" t-attf-class="{{ chatterTopbar.chatter.composerView ? 'o-has-active-button' : 'border-white' }}">
<div class="o_ChatterTopbar_controllers d-flex pe-2" t-if="chatterTopbar.chatter.threadView">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonSendMessage btn text-nowrap me-2"
type="button"
t-att-class="{
'o-active border-end': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog,
'o-bordered': chatterTopbar.chatter.hasExternalBorder,
'border-top border-start': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog and chatterTopbar.chatter.hasExternalBorder,
'o-active btn-odoo': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog,
'btn-odoo': !chatterTopbar.chatter.composerView,
'btn-light': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog,
}"
t-att-disabled="!chatterTopbar.chatter.hasWriteAccess"
data-hotkey="m"
@@ -19,12 +19,11 @@
>
Send message
</button>
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote btn btn-link"
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonLogNote btn text-nowrap"
type="button"
t-att-class="{
'o-active border-end border-start': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog,
'o-bordered': chatterTopbar.chatter.hasExternalBorder,
'border-top': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog and chatterTopbar.chatter.hasExternalBorder,
'o-active btn-odoo': chatterTopbar.chatter.composerView and chatterTopbar.chatter.composerView.composer.isLog,
'btn-light': chatterTopbar.chatter.composerView and !chatterTopbar.chatter.composerView.composer.isLog or !chatterTopbar.chatter.composerView,
}"
t-att-disabled="!chatterTopbar.chatter.hasWriteAccess"
t-on-click="chatterTopbar.chatter.onClickLogNote"
@@ -32,26 +31,24 @@
>
Log note
</button>
</t>
<t t-if="chatterTopbar.chatter.hasActivities">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity btn btn-link" type="button" t-att-disabled="!chatterTopbar.chatter.hasWriteAccess" t-on-click="chatterTopbar.chatter.onClickScheduleActivity" data-hotkey="shift+a">
<i class="fa fa-clock-o me-1"/>
<span>Schedule activity</span>
</button>
</t>
<div class="flex-grow-1"/>
</div>
<div class="o_ChatterTopbar_tools position-relative d-flex flex-grow-1 border-start border-bottom ps-2">
<t t-if="chatterTopbar.chatter.hasActivities">
<button class="o_ChatterTopbar_button o_ChatterTopbar_buttonScheduleActivity btn btn-light text-nowrap" type="button" t-att-disabled="!chatterTopbar.chatter.hasWriteAccess" t-on-click="chatterTopbar.chatter.onClickScheduleActivity" data-hotkey="shift+a">
<i class="fa fa-clock-o me-1"/>
<span>Activities</span>
</button>
</t>
<div class="flex-grow-1 border-start ms-2 pe-2"/>
<div class="o_ChatterTopbar_rightSection flex-grow-1 flex-shrink-0 justify-content-end d-flex">
<button t-if="chatterTopbar.chatter.thread.allAttachments.length === 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-link" type="button" t-att-disabled="!chatterTopbar.chatter.hasWriteAccess" t-on-click="chatterTopbar.chatter.onClickButtonAddAttachments" >
<i class="fa fa-paperclip me-1" role="img" aria-label="Attachments"/>
<t t-if="!chatterTopbar.chatter.isShowingAttachmentsLoading">
<span class="o_ChatterTopbar_buttonAddAttachmentsText">Attach files</span>
</t>
<button t-if="chatterTopbar.chatter.thread.allAttachments.length === 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonAddAttachments btn btn-light btn-primary" type="button" t-att-disabled="!chatterTopbar.chatter.hasWriteAccess" t-on-click="chatterTopbar.chatter.onClickButtonAddAttachments" >
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<t t-if="chatterTopbar.chatter.isShowingAttachmentsLoading">
<i class="o_ChatterTopbar_buttonAttachmentsCountLoader fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
</t>
</button>
<button t-if="chatterTopbar.chatter.thread.allAttachments.length > 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-link" type="button" t-att-disabled="!chatterTopbar.chatter.hasReadAccess" t-att-aria-expanded="chatterTopbar.chatter.attachmentBoxView ? 'true' : 'false'" t-on-click="chatterTopbar.chatter.onClickButtonToggleAttachments">
<i class="fa fa-paperclip me-1" role="img" aria-label="Attachments"/>
<button t-if="chatterTopbar.chatter.thread.allAttachments.length > 0" class="o_ChatterTopbar_button o_ChatterTopbar_buttonToggleAttachments btn btn-light btn-primary" type="button" t-att-disabled="!chatterTopbar.chatter.hasReadAccess" t-att-aria-expanded="chatterTopbar.chatter.attachmentBoxView ? 'true' : 'false'" t-on-click="chatterTopbar.chatter.onClickButtonToggleAttachments">
<i class="fa fa-paperclip fa-lg me-1" role="img" aria-label="Attachments"/>
<t t-if="!chatterTopbar.chatter.isShowingAttachmentsLoading">
<span class="o_ChatterTopbar_buttonCount o_ChatterTopbar_buttonAttachmentsCount" t-esc="chatterTopbar.attachmentButtonText"/>
</t>
@@ -60,18 +57,19 @@
</t>
</button>
<t t-if="chatterTopbar.chatter.hasFollowers and chatterTopbar.chatter.thread">
<FollowerListMenu
className="'o_ChatterTopbar_followerListMenu'"
record="chatterTopbar.chatter.followerListMenuView"
/>
<t t-if="chatterTopbar.chatter.followButtonView">
<FollowButton
className="'o_ChatterTopbar_followButton'"
record="chatterTopbar.chatter.followButtonView"
/>
</t>
<FollowerListMenu
className="'o_ChatterTopbar_followerListMenu'"
record="chatterTopbar.chatter.followerListMenuView"
/>
</t>
</div>
</div>
</div>
<t t-if="chatterTopbar.chatter.hasTopbarCloseButton">
<button class="o_ChatterTopbar_buttonClose btn btn-dark flex-shrink-0 rounded-bottom-3" aria-label="Close" t-on-click="chatterTopbar.chatter.onClickChatterTopbarClose">
@@ -12,7 +12,6 @@
'o-is-in-thread-view bg-white': composerView.threadView,
'o-is-compact': composerView.isCompact,
'o-messaging-in-editing': composerView.messageViewInEditing,
'bg-100': !composerView.messageViewInEditing and !composerView.threadView,
'pt-3 pb-4': composerView.hasCurrentPartnerAvatar and composerView.hasHeader,
'py-4': composerView.hasCurrentPartnerAvatar and !composerView.hasHeader and !composerView.threadView,
'py-1': composerView.hasCurrentPartnerAvatar and !composerView.hasHeader and composerView.threadView,
@@ -5,9 +5,9 @@
<t t-if="followButtonView">
<div class="o_FollowButton d-flex" t-attf-class="{{ className }}" t-ref="root">
<t t-if="followButtonView.chatterOwner.thread and followButtonView.chatterOwner.thread.isCurrentPartnerFollowing">
<button class="o_FollowButton_unfollow btn" t-att-class="{ 'o_ChatterTopbar_button': followButtonView.chatterOwner, 'o-following text-success': !followButtonView.isUnfollowButtonHighlighted, 'o-unfollow text-warning': followButtonView.isUnfollowButtonHighlighted }" t-att-disabled="followButtonView.isDisabled" t-on-click="followButtonView.onClickUnfollow" t-on-mouseenter="followButtonView.onMouseEnterUnfollow" t-on-mouseleave="followButtonView.onMouseleaveUnfollow">
<button class="o_FollowButton_unfollow btn pe-0" t-att-class="{ 'o_ChatterTopbar_button': followButtonView.chatterOwner, 'o-following text-success': !followButtonView.isUnfollowButtonHighlighted, 'o-unfollow text-warning': followButtonView.isUnfollowButtonHighlighted }" t-att-disabled="followButtonView.isDisabled" t-on-click="followButtonView.onClickUnfollow" t-on-mouseenter="followButtonView.onMouseEnterUnfollow" t-on-mouseleave="followButtonView.onMouseleaveUnfollow">
<t t-if="followButtonView.isUnfollowButtonHighlighted">
<i class="fa fa-times"/> Unfollow
<i class="oi oi-close oi-large"/> Unfollow
</t>
<t t-else="">
<i class="fa fa-check"/> Following
@@ -15,7 +15,7 @@
</button>
</t>
<t t-else="">
<button class="o_FollowButton_follow btn btn-link text-600" t-att-disabled="followButtonView.isDisabled" t-on-click="followButtonView.onClickFollow" t-att-class="{ 'o_ChatterTopbar_button': followButtonView.chatterOwner }">
<button class="o_FollowButton_follow btn btn-link text-600 pe-0" t-att-disabled="followButtonView.isDisabled" t-on-click="followButtonView.onClickFollow" t-att-class="{ 'o_ChatterTopbar_button': followButtonView.chatterOwner }">
Follow
</button>
</t>
@@ -5,8 +5,8 @@
<t t-if="followerListMenuView">
<div class="o_FollowerListMenu position-relative d-flex" t-attf-class="{{ className }}" t-on-keydown="followerListMenuView.onKeydown" t-ref="root">
<div class="o_FollowerListMenu_followers d-flex" t-ref="dropdown">
<button class="o_FollowerListMenu_buttonFollowers o_ChatterTopbar_button btn btn-link" t-att-disabled="followerListMenuView.isDisabled" t-att-aria-expanded="followerListMenuView.isDropdownOpen ? 'true' : 'false'" t-on-click="followerListMenuView.onClickFollowersButton" aria-label="Show Followers">
<i class="fa fa-user" role="img"/>
<button class="o_FollowerListMenu_buttonFollowers o_ChatterTopbar_button btn btn-light" t-att-disabled="followerListMenuView.isDisabled" t-att-aria-expanded="followerListMenuView.isDropdownOpen ? 'true' : 'false'" t-on-click="followerListMenuView.onClickFollowersButton" aria-label="Show Followers">
<i class="fa fa-user-o" role="img"/>
<span class="o_FollowerListMenu_buttonFollowersCount ps-1" t-esc="followerListMenuView.followerViews.length"/>
</button>
@@ -2,6 +2,10 @@
// Layout
// ------------------------------------------------------------------
.o_Message {
max-width: var(--Chatter-max-width, none);
}
.o_Message_actionListContainer {
z-index: 10; // Place the element in front of the Composer when they overlap
}
@@ -2,8 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { sprintf } from '@web/core/utils/strings';
import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'ChatterTopbar',
@@ -14,23 +13,15 @@ registerModel({
attachmentButtonText: attr({
compute() {
if (!this.chatter || !this.chatter.thread) {
return;
return clear();
}
const attachments = this.chatter.thread.allAttachments;
switch (attachments.length) {
case 0:
return this.env._t("Attach files");
case 1:
return this.env._t("1 file");
case 2:
return this.env._t("2 files");
default:
return sprintf(
this.env._t("%(attachmentCount)s files"),
{ attachmentCount: attachments.length }
);
if (attachments.length === 0) {
return clear();
}
return attachments.length;
},
default: "",
}),
chatter: one('Chatter', {
identifying: true,
@@ -3,16 +3,32 @@
// ------------------------------------------------------------------
.o_FormRenderer_chatterContainer {
flex: 1 0 auto;
--ChatterTopbar-padding-v: #{map-get($spacers, 2)};
@include media-breakpoint-down(md) {
--ChatterTopbar-padding-left: #{$o-horizontal-padding};
--ChatterTopbar-padding-right: #{$o-horizontal-padding};
}
&.o-aside {
--Chatter-max-width: #{$o-chatter-min-width};
--ChatterTopbar-padding-left: #{$o-horizontal-padding};
--ChatterTopbar-padding-right: #{$o-horizontal-padding};
flex-shrink: 0;
flex-grow: 0;
border-left: $border-width solid;
padding: map-get($spacers, 0);
width: $o-chatter-min-width;
min-width: $o-chatter-min-width;
width: auto;
.o_Message.o-not-discussion {
border-width: $border-width 0;
}
.o_ChatterContainer {
width: auto;
}
}
&.o-isInFormSheetBg {
@@ -21,11 +37,11 @@
border-top: 1px solid $border-color;
}
.o_ChatterContainer {
@include make-container-max-widths();
&:not(.o-aside) .o_ChatterContainer {
@include make-container-max-widths();
margin-left: auto;
margin-right: auto;
margin-left: auto;
margin-right: auto;
}
}
@@ -9,7 +9,7 @@ QUnit.module('components', {}, function () {
QUnit.module('chatter_topbar_tests.js');
QUnit.test('base rendering', async function (assert) {
assert.expect(8);
assert.expect(7);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({});
@@ -50,11 +50,6 @@ QUnit.test('base rendering', async function (assert) {
0,
"attachments button should not have a loader"
);
assert.strictEqual(
document.querySelectorAll(`.o_ChatterTopbar_buttonAddAttachmentsText`).length,
1,
"attachments button should have a text"
);
assert.strictEqual(
document.querySelectorAll(`.o_ChatterTopbar_followerListMenu`).length,
1,
@@ -63,7 +58,7 @@ QUnit.test('base rendering', async function (assert) {
});
QUnit.test('base disabled rendering', async function (assert) {
assert.expect(8);
assert.expect(6);
const { openView } = await start();
await openView({
@@ -96,16 +91,6 @@ QUnit.test('base disabled rendering', async function (assert) {
0,
"attachments button should not have a loader"
);
assert.strictEqual(
document.querySelectorAll(`.o_ChatterTopbar_buttonAddAttachmentsText`).length,
1,
"attachments button should have a text"
);
assert.strictEqual(
document.querySelector(`.o_ChatterTopbar_buttonAddAttachmentsText`).textContent,
'Attach files',
"Add attachments button content should be 'Attach files'"
);
});
QUnit.test('attachment loading is delayed', async function (assert) {
@@ -193,7 +178,7 @@ QUnit.test('attachment counter while loading attachments', async function (asser
});
QUnit.test('attachment counter transition when attachments become loaded)', async function (assert) {
assert.expect(7);
assert.expect(6);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({});
@@ -243,15 +228,10 @@ QUnit.test('attachment counter transition when attachments become loaded)', asyn
0,
"attachments button should not have a loader"
);
assert.strictEqual(
document.querySelectorAll(`.o_ChatterTopbar_buttonAddAttachmentsText`).length,
1,
"attachments button should have a counter"
);
});
QUnit.test('attachment counter without attachments', async function (assert) {
assert.expect(4);
assert.expect(2);
const pyEnv = await startServer();
const resPartnerId1 = pyEnv['res.partner'].create({});
@@ -272,16 +252,6 @@ QUnit.test('attachment counter without attachments', async function (assert) {
1,
"should have an attachments button in chatter menu"
);
assert.strictEqual(
document.querySelectorAll(`.o_ChatterTopbar_buttonAddAttachmentsText`).length,
1,
"attachments button should have a counter"
);
assert.strictEqual(
document.querySelector(`.o_ChatterTopbar_buttonAddAttachmentsText`).textContent,
'Attach files',
'attachment counter content should contain "Attach files"'
);
});
QUnit.test('attachment counter with attachments', async function (assert) {
@@ -327,7 +297,7 @@ QUnit.test('attachment counter with attachments', async function (assert) {
);
assert.strictEqual(
document.querySelector(`.o_ChatterTopbar_buttonAttachmentsCount`).textContent,
'2 files',
'2',
'attachment counter content should contain "2 files"'
);
});
@@ -838,7 +838,7 @@ QUnit.test('Attachments that have been unlinked from server should be visually u
);
assert.strictEqual(
document.querySelector('.o_ChatterTopbar_buttonCount').textContent,
'2 files',
'2',
"Partner1 should have 2 attachments initially"
);
@@ -850,7 +850,7 @@ QUnit.test('Attachments that have been unlinked from server should be visually u
await click('.o_pager_previous');
assert.strictEqual(
document.querySelector('.o_ChatterTopbar_buttonCount').textContent,
'1 file',
'1',
"Partner1 should now have 1 attachment after it has been unlinked from server"
);
});
@@ -1,5 +1,4 @@
$o-webclient-background-color: $o-gray-100 !default;
$o-control-panel-background-color: $o-view-background-color !default;
$o-list-footer-color: null !default;
$o-list-group-header-color: lighten($o-brand-lightsecondary, 10%) !default;
@@ -1,12 +1,11 @@
.o_control_panel {
border-bottom: 1px solid $border-color;
@include o-webclient-padding($top: 5px, $bottom: 5px);
@include o-webclient-padding($top: map-get($spacers, 2), $bottom: map-get($spacers, 2));
background-color: $o-control-panel-background-color;
> div {
display: flex;
min-height: 30px;
margin: 5px 0px;
min-height: $o-statusbar-height;
}
@include media-breakpoint-up(md) {
@@ -4,7 +4,7 @@
<t t-name="web.FormControlPanel" owl="1">
<div class="o_control_panel" t-ref="root">
<div t-if="display['top']" class="o_cp_top">
<div class="o_cp_top_left w-auto d-flex flex-grow-1">
<div class="o_cp_top_left w-auto d-flex flex-grow-1 align-items-center">
<t t-if="display['top-left']">
<t t-slot="control-panel-breadcrumb">
<t t-if="env.isSmall">
@@ -17,7 +17,7 @@
<t t-slot="control-panel-status-indicator" />
</t>
</div>
<div class="o_cp_bottom_right w-auto flex-shrink-0 align-items-center">
<div class="o_cp_bottom_right w-auto flex-shrink-0">
<t t-slot="control-panel-action-menu" t-if="display['bottom-left']"/>
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
<Pager t-props="pagerProps"/>
@@ -1068,7 +1068,6 @@
.o_form_sheet_bg {
flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety
width: $o-form-sheet-min-width + 2 * $o-horizontal-padding;
// max-width: map-get($container-max-widths, xl) + 2 * $o-horizontal-padding; // would be logical but breaks no-chatter form views
padding: 0 $o-horizontal-padding;
overflow: auto;