[IMP] mail: introduce PartnerImStatusIconView model

In preparation to move common code of guest and user to dedicated model.

Part of task-2883466

Part-of: odoo/odoo#93731
This commit is contained in:
Sébastien Theys
2022-06-17 14:14:32 +02:00
parent c0d8ed8196
commit e8023a1c89
19 changed files with 223 additions and 34 deletions
@@ -2,13 +2,13 @@
<templates xml:space="preserve">
<t t-inherit="mail.PartnerImStatusIcon" t-inherit-mode="extension">
<xpath expr="//*[@name='root']" position="inside">
<t t-if="partner.im_status === 'leave_online'">
<t t-if="partnerImStatusIconView.partner.im_status === 'leave_online'">
<i class="o_PartnerImStatusIcon_icon o-online fa fa-plane fa-stack-1x" title="Online" role="img" aria-label="User is online"/>
</t>
<t t-if="partner.im_status === 'leave_away'">
<t t-if="partnerImStatusIconView.partner.im_status === 'leave_away'">
<i class="o_PartnerImStatusIcon_icon o-away fa fa-plane fa-stack-1x text-warning" title="Away" role="img" aria-label="User is away"/>
</t>
<t t-if="partner.im_status === 'leave_offline'">
<t t-if="partnerImStatusIconView.partner.im_status === 'leave_offline'">
<i class="o_PartnerImStatusIcon_icon o-offline fa fa-plane fa-stack-1x text-700" title="Out of office" role="img" aria-label="User is out of office"/>
</t>
</xpath>
@@ -5,14 +5,14 @@
<div class="o_ChannelInvitationFormSelectablePartner d-flex align-items-center px-3 py-1 btn-light" t-on-click="() => channelInvitationFormSelectablePartnerView.channelInvitationFormOwner.onClickSelectablePartner(channelInvitationFormSelectablePartnerView.partner)" t-att-data-partner-id="channelInvitationFormSelectablePartnerView.partner.id" t-attf-class="{{ className }}" t-ref="root">
<div class="o_ChannelInvitationFormSelectablePartner_avatarContainer position-relative flex-shrink-0">
<img class="o_ChannelInvitationFormSelectablePartner_avatar w-100 h-100 rounded-circle" t-att-src="channelInvitationFormSelectablePartnerView.partner.avatarUrl" alt="Avatar"/>
<t t-if="channelInvitationFormSelectablePartnerView.partner.im_status and channelInvitationFormSelectablePartnerView.partner.im_status !== 'im_partner'">
<t t-if="channelInvitationFormSelectablePartnerView.partnerImStatusIconView">
<PartnerImStatusIcon
className="'o_ChannelInvitationFormSelectablePartner_imStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center text-white'"
classNameObj="{
'o_ChannelInvitationFormSelectablePartner_imStatusIcon-mobile': messaging.device.isSmall,
'small' : !messaging.device.isSmall,
}"
partner="channelInvitationFormSelectablePartnerView.partner"
record="channelInvitationFormSelectablePartnerView.partnerImStatusIconView"
/>
</t>
</div>
@@ -6,14 +6,14 @@
<div class="o_ChannelMember_avatarContainer position-relative flex-shrink-0 o_cursor_pointer" t-on-click="channelMemberView.onClickMemberAvatar">
<img class="o_ChannelMember_avatar rounded-circle w-100 h-100" t-attf-src="/mail/channel/{{ channelMemberView.channelMember.channel.id }}/partner/{{ channelMemberView.channelMember.partner.id }}/avatar_128" alt="Avatar"/>
<t t-if="channelMemberView.channelMember.partner.im_status and channelMemberView.channelMember.partner.im_status !== 'im_partner'">
<t t-if="channelMemberView.partnerImStatusIconView">
<PartnerImStatusIcon
className="'o_ChannelMember_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center text-light'"
classNameObj="{
'o-isDeviceSmall': messaging.device.isSmall,
'small': !messaging.device.isSmall,
}"
partner="channelMemberView.channelMember.partner"
record="channelMemberView.partnerImStatusIconView"
/>
</t>
</div>
@@ -17,9 +17,10 @@
</t>
<t t-if="composerSuggestion.partner">
<PartnerImStatusIcon
t-if="composerSuggestion.partnerImStatusIconView"
className="'o_ComposerSuggestion_partnerImStatusIcon flex-shrink-0 mr-1'"
hasBackground="false"
partner="composerSuggestion.record"
record="composerSuggestion.partnerImStatusIconView"
/>
<strong class="o_ComposerSuggestion_part1 flex-shrink-0 mw-100 pr-2 text-truncate"><t t-esc="composerSuggestion.record.nameOrDisplayName"/></strong>
<t t-if="composerSuggestion.record.email">
@@ -35,7 +35,7 @@
<t t-if="!messageView.isSquashed">
<div class="o_Message_authorAvatarContainer o_Message_sidebarItem position-relative mx-1">
<img class="o_Message_authorAvatar w-100 h-100 rounded-circle" t-att-class="{ 'o_Message_authorRedirect o_cursor_pointer': messageView.hasAuthorOpenChat, o_redirect: messageView.hasAuthorOpenChat }" t-att-src="messageView.message.avatarUrl" role="button" tabindex="0" t-on-click="messageView.onClickAuthorAvatar" t-att-title="messageView.hasAuthorOpenChat ? messageView.authorAvatarTitleText : ''" t-att-alt="messageView.hasAuthorOpenChat ? messageView.authorAvatarTitleText : ''"/>
<t t-if="messageView.message.author and messageView.message.author.im_status">
<t t-if="messageView.partnerImStatusIconView">
<PartnerImStatusIcon
className="'o_Message_partnerImStatusIcon d-flex align-items-center justify-content-center'"
classNameObj="{
@@ -46,7 +46,7 @@
'text-100': !messageView.message.isDiscussionOrNotification and !messageView.isSelected,
}"
hasOpenChat="messageView.hasAuthorOpenChat"
partner="messageView.message.author"
record="messageView.partnerImStatusIconView"
/>
</t>
</div>
@@ -8,9 +8,10 @@
<div class="o_NotificationListItem_imageContainer o_NotificationRequest_imageContainer o_NotificationRequest_sidebarItem position-relative">
<img class="o_NotificationListItem_image o_NotificationRequest_image w-100 h-100 rounded-circle" t-att-src="messaging.partnerRoot.avatarUrl" alt="Avatar of OdooBot"/>
<PartnerImStatusIcon
t-if="notificationRequestView.partnerImStatusIconView"
className="'o_NotificationListItem_partnerImStatusIcon o_NotificationRequest_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
classNameObj="{ 'o-isDeviceSmall': messaging.device.isSmall, 'small': !messaging.device.isSmall }"
partner="messaging.partnerRoot"
record="notificationRequestView.partnerImStatusIconView"
/>
</div>
</div>
@@ -12,10 +12,10 @@ export class PartnerImStatusIcon extends Component {
//--------------------------------------------------------------------------
/**
* @returns {Partner}
* @returns {PartnerImStatusIconView}
*/
get partner() {
return this.props.partner;
get partnerImStatusIconView() {
return this.props.record;
}
//--------------------------------------------------------------------------
@@ -31,7 +31,7 @@ export class PartnerImStatusIcon extends Component {
if (!this.props.hasOpenChat) {
return;
}
this.partner.openChat();
this.partnerImStatusIconView.partner.openChat();
}
}
@@ -42,13 +42,13 @@ Object.assign(PartnerImStatusIcon, {
hasOpenChat: false,
},
props: {
partner: Object,
hasBackground: { type: Boolean, optional: true },
/**
* Determines whether a click on `this` should open a chat with
* `this.partner`.
*/
hasOpenChat: { type: Boolean, optional: true },
record: Object,
},
template: 'mail.PartnerImStatusIcon',
});
@@ -2,14 +2,13 @@
<templates xml:space="preserve">
<t t-name="mail.PartnerImStatusIcon" owl="1">
<t t-if="partner">
<span class="o_PartnerImStatusIcon d-flex flex-column fa-stack"
t-att-class="{
'o-away': partner.im_status === 'away',
'o-bot': messaging.partnerRoot === partner,
'o-away': partnerImStatusIconView.partner.im_status === 'away',
'o-bot': messaging.partnerRoot === partnerImStatusIconView.partner,
'o-has-open-chat o_cursor_pointer': props.hasOpenChat,
'o-offline': partner.im_status === 'offline',
'o-online': partner.im_status === 'online',
'o-offline': partnerImStatusIconView.partner.im_status === 'offline',
'o-online': partnerImStatusIconView.partner.im_status === 'online',
}"
t-attf-class="{{ className }}"
t-on-click="_onClick"
@@ -20,20 +19,19 @@
<i class="o_PartnerImStatusIcon_outerBackground fa fa-circle fa-stack-1x"/>
<i class="o_PartnerImStatusIcon_innerBackground fa fa-circle fa-stack-1x text-white"/>
</t>
<t t-if="partner.im_status === 'online'">
<t t-if="partnerImStatusIconView.partner.im_status === 'online'">
<i class="o_PartnerImStatusIcon_icon o-online fa fa-circle fa-stack-1x" title="Online" role="img" aria-label="User is online"/>
</t>
<t t-if="partner.im_status === 'away'">
<t t-if="partnerImStatusIconView.partner.im_status === 'away'">
<i class="o_PartnerImStatusIcon_icon o-away fa fa-circle fa-stack-1x text-warning" title="Idle" role="img" aria-label="User is idle"/>
</t>
<t t-if="partner.im_status === 'offline'">
<t t-if="partnerImStatusIconView.partner.im_status === 'offline'">
<i class="o_PartnerImStatusIcon_icon o-offline fa fa-circle-o fa-stack-1x text-700" title="Offline" role="img" aria-label="User is offline"/>
</t>
<t t-if="partner === messaging.partnerRoot">
<t t-if="partnerImStatusIconView.partner === messaging.partnerRoot">
<i class="o_PartnerImStatusIcon_icon o-bot fa fa-heart fa-stack-1x" title="Bot" role="img" aria-label="User is a bot"/>
</t>
</span>
</t>
</t>
</templates>
@@ -11,14 +11,14 @@
<div class="o_NotificationListItem_sidebar o_ThreadNeedactionPreview_sidebar m-1">
<div class="o_NotificationListItem_imageContainer o_ThreadNeedactionPreview_imageContainer o_ThreadNeedactionPreview_sidebarItem position-relative">
<img class="o_NotificationListItem_image o_ThreadNeedactionPreview_image w-100 h-100" t-att-src="image()" alt="Thread Image"/>
<t t-if="threadNeedactionPreviewView.thread.correspondent and threadNeedactionPreviewView.thread.correspondent.im_status">
<t t-if="threadNeedactionPreviewView.partnerImStatusIconView">
<PartnerImStatusIcon
className="'o_NotificationListItem_partnerImStatusIcon o_ThreadNeedactionPreview_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
classNameObj="{
'o-isDeviceSmall': messaging.device.isSmall,
'small': !messaging.device.isSmall,
}"
partner="threadNeedactionPreviewView.thread.correspondent"
record="threadNeedactionPreviewView.partnerImStatusIconView"
/>
</t>
</div>
@@ -11,7 +11,7 @@
<div class="o_NotificationListItem_sidebar o_ThreadPreview_sidebar m-1">
<div class="o_NotificationListItem_imageContainer o_ThreadPreview_imageContainer o_ThreadPreview_sidebarItem position-relative">
<img class="o_NotificationListItem_image o_ThreadPreview_image w-100 h-100 rounded-circle" t-att-src="image()" alt="Thread Image"/>
<t t-if="threadPreviewView.thread.correspondent and threadPreviewView.thread.correspondent.im_status">
<t t-if="threadPreviewView.partnerImStatusIconView">
<PartnerImStatusIcon
className="'o_NotificationListItem_partnerImStatusIcon o_ThreadPreview_partnerImStatusIcon position-absolute bottom-0 end-0 d-flex align-items-center justify-content-center'"
classNameObj="{
@@ -19,7 +19,7 @@
'small': !messaging.device.isSmall,
'o-muted': threadPreviewView.thread.localMessageUnreadCounter === 0,
}"
partner="threadPreviewView.thread.correspondent"
record="threadPreviewView.partnerImStatusIconView"
/>
</t>
</div>
@@ -2,10 +2,20 @@
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'ChannelInvitationFormSelectablePartnerView',
identifyingFields: ['channelInvitationFormOwner', 'partner'],
recordMethods: {
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.partner.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
channelInvitationFormOwner: one('ChannelInvitationForm', {
inverse: 'selectablePartnerViews',
@@ -17,5 +27,11 @@ registerModel({
readonly: true,
required: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'channelInvitationFormSelectablePartnerViewOwner',
isCausal: true,
readonly: true,
}),
},
});
@@ -2,6 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'ChannelMemberView',
@@ -27,6 +28,13 @@ registerModel({
}
this.channelMember.partner.openProfile();
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.channelMember.partner && this.channelMember.partner.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
channelMemberListCategoryViewOwner: one('ChannelMemberListCategoryView', {
@@ -39,5 +47,11 @@ registerModel({
readonly: true,
required: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'channelMemberViewOwner',
isCausal: true,
readonly: true,
}),
},
});
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, replace } from '@mail/model/model_field_command';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
import { sprintf } from '@web/core/utils/strings';
/**
@@ -66,6 +66,13 @@ registerModel({
return this.thread.name;
}
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.partner && this.partner.isImStatusSet ? insertAndReplace() : clear();
},
/**
* @private
* @returns {FieldCommand}
@@ -143,6 +150,12 @@ registerModel({
partner: one('Partner', {
readonly: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'composerSuggestionViewOwner',
isCausal: true,
readonly: true,
}),
thread: one('Thread', {
readonly: true,
}),
@@ -294,6 +294,13 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.message.author && this.message.author.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
/**
@@ -439,6 +446,12 @@ registerModel({
messagingAsClickedMessageView: one('Messaging', {
inverse: 'clickedMessageView',
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'messageViewOwner',
isCausal: true,
readonly: true,
}),
/**
* States the thread view that is displaying this messages (if any).
*/
@@ -2,7 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
import { clear, insertAndReplace } from '@mail/model/model_field_command';
import { sprintf } from '@web/core/utils/strings';
@@ -23,6 +23,13 @@ registerModel({
{ odoobotName: this.messaging.partnerRoot.nameOrDisplayName },
);
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.messaging.partnerRoot.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
headerText: attr({
@@ -33,5 +40,11 @@ registerModel({
required: true,
readonly: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'notificationRequestViewOwner',
isCausal: true,
readonly: true,
}),
},
});
+11
View File
@@ -346,6 +346,13 @@ registerModel({
_computeDisplayName() {
return this.display_name || this.user && this.user.display_name;
},
/**
* @private
* @returns {boolean}
*/
_computeIsImStatusSet() {
return Boolean(this.im_status && this.im_status !== 'im_partner');
},
/**
* @private
* @returns {boolean}
@@ -410,6 +417,10 @@ registerModel({
required: true,
}),
im_status: attr(),
isImStatusSet: attr({
compute: '_computeIsImStatusSet',
readonly: true,
}),
/**
* States whether this partner is online.
*/
@@ -0,0 +1,83 @@
/** @odoo-module **/
import { one } from '@mail/model/model_field';
import { registerModel } from '@mail/model/model_core';
import { clear, replace } from '@mail/model/model_field_command';
registerModel({
name: 'PartnerImStatusIconView',
identifyingFields: [[
'channelInvitationFormSelectablePartnerViewOwner',
'channelMemberViewOwner',
'composerSuggestionViewOwner',
'messageViewOwner',
'notificationRequestViewOwner',
'threadNeedactionPreviewViewOwner',
'threadPreviewViewOwner',
]],
recordMethods: {
/**
* @private
* @returns {FieldCommand}
*/
_computePartner() {
if (this.channelInvitationFormSelectablePartnerViewOwner) {
return replace(this.channelInvitationFormSelectablePartnerViewOwner.partner);
}
if (this.channelMemberViewOwner) {
return replace(this.channelMemberViewOwner.channelMember.partner);
}
if (this.composerSuggestionViewOwner) {
return replace(this.composerSuggestionViewOwner.partner);
}
if (this.messageViewOwner) {
return replace(this.messageViewOwner.message.author);
}
if (this.notificationRequestViewOwner) {
return replace(this.messaging.partnerRoot);
}
if (this.threadNeedactionPreviewViewOwner) {
return replace(this.threadNeedactionPreviewViewOwner.thread.correspondent);
}
if (this.threadPreviewViewOwner) {
return replace(this.threadPreviewViewOwner.thread.correspondent);
}
return clear();
},
},
fields: {
channelInvitationFormSelectablePartnerViewOwner: one('ChannelInvitationFormSelectablePartnerView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
channelMemberViewOwner: one('ChannelMemberView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
composerSuggestionViewOwner: one('ComposerSuggestion', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
messageViewOwner: one('MessageView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
notificationRequestViewOwner: one('NotificationRequestView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
threadNeedactionPreviewViewOwner: one('ThreadNeedactionPreviewView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
threadPreviewViewOwner: one('ThreadPreviewView', {
inverse: 'partnerImStatusIconView',
readonly: true,
}),
partner: one('Partner', {
compute: '_computePartner',
readonly: true,
required: true,
}),
},
});
@@ -75,6 +75,13 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.thread.correspondent && this.thread.correspondent.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
inlineLastNeedactionMessageAsOriginThreadBody: attr({
@@ -87,7 +94,7 @@ registerModel({
readonly: true,
}),
lastTrackingValue: one('TrackingValue', {
compute:'_computeLastTrackingValue',
compute: '_computeLastTrackingValue',
readonly: true,
}),
/**
@@ -105,6 +112,12 @@ registerModel({
readonly: true,
required: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'threadNeedactionPreviewViewOwner',
isCausal: true,
readonly: true,
}),
thread: one('Thread', {
inverse: 'threadNeedactionPreviewViews',
readonly: true,
@@ -74,6 +74,13 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computePartnerImStatusIconView() {
return this.thread.correspondent && this.thread.correspondent.isImStatusSet ? insertAndReplace() : clear();
},
},
fields: {
inlineLastMessageBody: attr({
@@ -86,7 +93,7 @@ registerModel({
readonly: true,
}),
lastTrackingValue: one('TrackingValue', {
compute:'_computeLastTrackingValue',
compute: '_computeLastTrackingValue',
readonly: true,
}),
/**
@@ -104,6 +111,12 @@ registerModel({
readonly: true,
required: true,
}),
partnerImStatusIconView: one('PartnerImStatusIconView', {
compute: '_computePartnerImStatusIconView',
inverse: 'threadPreviewViewOwner',
isCausal: true,
readonly: true,
}),
thread: one('Thread', {
inverse: 'threadPreviewViews',
readonly: true,