[IMP] mail, *: use PopoverView for Message/notificationIcon popover

*: sms, snailmail

Task-2864563

Part-of: odoo/odoo#98497
This commit is contained in:
Alexandre Kühn
2022-08-30 20:22:52 +02:00
parent 2f5da9729a
commit 4e697ff6d0
17 changed files with 302 additions and 109 deletions
@@ -1,12 +1,12 @@
/** @odoo-module **/
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { useUpdate } from '@mail/component_hooks/use_update';
import { useUpdateToModel } from '@mail/component_hooks/use_update_to_model';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
import { _lt } from 'web.core';
import Popover from "web.Popover";
const { Component, useRef } = owl;
@@ -21,6 +21,7 @@ export class Message extends Component {
setup() {
super.setup();
useComponentToModel({ fieldName: 'component' });
useRefToModel({ fieldName: 'notificationIconRef', refName: 'notificationIcon' });
useUpdateToModel({ methodName: 'onComponentUpdate' });
useUpdate({ func: () => this._update() });
/**
@@ -259,7 +260,6 @@ export class Message extends Component {
Object.assign(Message, {
props: { record: Object },
template: 'mail.Message',
components: { Popover },
});
registerMessagingComponent(Message);
@@ -114,18 +114,13 @@
<div t-att-class="{ 'ms-2': messageView.isInChatWindowAndIsAlignedRight }">
<t t-if="messageView.message.failureNotifications.length > 0">
<span class="o_Message_notificationIconClickable o-error cursor-pointer text-danger" role="button" tabindex="0" t-on-click="messageView.onClickFailure">
<i name="failureIcon" class="o_Message_notificationIcon fa fa-envelope" role="img" aria-label="Delivery failure"/>
<i class="o_Message_notificationIcon" t-att-class="messageView.failureNotificationIconClassName" role="img" aria-label="Delivery failure"/> <span t-if="messageView.failureNotificationIconLabel" t-out="messageView.failureNotificationIconLabel"/>
</span>
</t>
<t t-else="">
<Popover>
<span class="o_Message_notificationIconClickable text-600 cursor-pointer">
<i name="notificationIcon" class="o_Message_notificationIcon fa fa-envelope-o"/>
</span>
<t t-set-slot="opened">
<MessageNotificationPopoverContent record="messageView"/>
</t>
</Popover>
<span class="o_Message_notificationIconClickable text-600 cursor-pointer" t-on-click="messageView.onClickNotificationIcon" t-ref="notificationIcon">
<i class="o_Message_notificationIcon" t-att-class="messageView.notificationIconClassName"/> <span t-if="messageView.notificationIconLabel" t-out="messageView.notificationIconLabel"/>
</span>
</t>
</div>
</t>
@@ -7,9 +7,9 @@ const { Component } = owl;
export class MessageNotificationPopoverContent extends Component {
/**
* @returns {MessageView}
* @returns {MessageNotificationPopoverContentView}
*/
get messageView() {
get messageNotificationPopoverContentView() {
return this.props.record;
}
@@ -2,20 +2,16 @@
<templates xml:space="preserve">
<t t-name="mail.MessageNotificationPopoverContent" owl="1">
<t t-if="messageView">
<div class="o_MessageNotificationPopoverContent" t-attf-class="{{ className }}" t-ref="root">
<t t-if="messageView.message">
<t t-foreach="messageView.message.notifications" t-as="notification" t-key="notification.localId">
<div class="o_MessageNotificationPopoverContent_notification">
<i class="o_MessageNotificationPopoverContent_notificationIcon me-2" t-att-class="notification.iconClass" t-att-title="notification.iconTitle" role="img"/>
<t t-if="notification.partner">
<span class="o_MessageNotificationPopoverContent_notificationPartnerName" t-esc="notification.partner.nameOrDisplayName"/>
</t>
</div>
<div class="o_MessageNotificationPopoverContent m-2" t-attf-class="{{ className }}" t-ref="root">
<t t-foreach="messageNotificationPopoverContentView.messageView.message.notifications" t-as="notification" t-key="notification.localId">
<div class="o_MessageNotificationPopoverContent_notification">
<i class="o_MessageNotificationPopoverContent_notificationIcon me-2" t-att-class="notification.iconClass" t-att-title="notification.iconTitle" role="img"/>
<t t-if="notification.partner">
<span class="o_MessageNotificationPopoverContent_notificationPartnerName" t-esc="notification.partner.nameOrDisplayName"/>
</t>
</t>
</div>
</t>
</div>
</t>
</div>
</t>
</templates>
@@ -0,0 +1,17 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
registerModel({
name: 'MessageNotificationPopoverContentView',
fields: {
messageView: one('MessageView', {
related: 'popoverViewOwner.messageViewOwnerAsNotificationContent',
}),
popoverViewOwner: one('PopoverView', {
identifying: true,
inverse: 'messageNotificationPopoverContentView',
}),
},
});
@@ -96,6 +96,9 @@ registerModel({
markEventHandled(ev, 'Message.ClickFailure');
this.message.openResendAction();
},
onClickNotificationIcon() {
this.update({ notificationPopoverView: {} });
},
/**
* @param {MouseEvent} ev
*/
@@ -230,6 +233,20 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeFailureNotificationIconClassName() {
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeFailureNotificationIconLabel() {
return clear();
},
/**
* @private
* @returns {boolean}
@@ -410,6 +427,20 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {string}
*/
_computeNotificationIconClassName() {
return clear();
},
/**
* @private
* @returns {FieldCommand}
*/
_computeNotificationIconLabel() {
return clear();
},
/**
* @private
* @returns {FieldCommand}
@@ -484,6 +515,14 @@ registerModel({
compute: '_computeExtraClass',
default: '',
}),
failureNotificationIconClassName: attr({
compute: '_computeFailureNotificationIconClassName',
default: 'fa fa-envelope',
}),
failureNotificationIconLabel: attr({
compute: '_computeFailureNotificationIconLabel',
default: '',
}),
/**
* Determines whether author open chat feature is enabled on message.
*/
@@ -603,6 +642,19 @@ registerModel({
messagingAsClickedMessageView: one('Messaging', {
inverse: 'clickedMessageView',
}),
notificationIconClassName: attr({
compute: '_computeNotificationIconClassName',
default: 'fa fa-envelope-o',
}),
notificationIconLabel: attr({
compute: '_computeNotificationIconLabel',
default: '',
}),
notificationIconRef: attr(),
notificationPopoverView: one('PopoverView', {
inverse: 'messageViewOwnerAsNotificationContent',
isCausal: true,
}),
personaImStatusIconView: one('PersonaImStatusIconView', {
compute: '_computePersonaImStatusIconView',
inverse: 'messageViewOwner',
@@ -53,6 +53,9 @@ registerModel({
if (this.messageActionViewOwnerAsReaction) {
return this.messageActionViewOwnerAsReaction.actionRef;
}
if (this.messageViewOwnerAsNotificationContent) {
return this.messageViewOwnerAsNotificationContent.notificationIconRef;
}
return clear();
},
/**
@@ -79,6 +82,9 @@ registerModel({
if (this.emojiPickerView) {
return this.emojiPickerView;
}
if (this.messageNotificationPopoverContentView) {
return this.messageNotificationPopoverContentView;
}
return clear();
},
/**
@@ -108,6 +114,9 @@ registerModel({
if (this.emojiPickerView) {
return 'EmojiPickerView';
}
if (this.messageNotificationPopoverContentView) {
return 'MessageNotificationPopoverContent';
}
return clear();
},
/**
@@ -133,6 +142,16 @@ registerModel({
}
return clear();
},
/**
* @private
* @returns {Object|FieldCommand}
*/
_computeMessageNotificationPopoverContentView() {
if (this.messageViewOwnerAsNotificationContent) {
return {};
}
return clear();
},
/**
* @private
* @returns {string}
@@ -249,6 +268,15 @@ registerModel({
identifying: true,
inverse: 'reactionPopoverView',
}),
messageNotificationPopoverContentView: one('MessageNotificationPopoverContentView', {
compute: '_computeMessageNotificationPopoverContentView',
inverse: 'popoverViewOwner',
isCausal: true,
}),
messageViewOwnerAsNotificationContent: one('MessageView', {
identifying: true,
inverse: 'notificationPopoverView',
}),
/**
* Position of the popover view relative to its anchor point.
* Valid values: 'top', 'right', 'bottom', 'left'
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.Message" t-inherit-mode="extension">
<xpath expr="//*[@name='failureIcon']" position="replace">
<t t-if="messageView.message.message_type === 'sms'">
<i class="o_Message_notificationIcon fa fa-mobile"/> SMS
</t>
<t t-else="">$0</t>
</xpath>
<xpath expr="//*[@name='notificationIcon']" position="replace">
<t t-if="messageView.message.message_type === 'sms'">
<i class="o_Message_notificationIcon fa fa-mobile"/> SMS
</t>
<t t-else="">$0</t>
</xpath>
</t>
</templates>
@@ -0,0 +1,44 @@
/** @odoo-module **/
import { patchRecordMethods } from '@mail/model/model_core';
// ensure that the model definition is loaded before the patch
import '@mail/models/message_view';
patchRecordMethods('MessageView', {
/**
* @override
*/
_computeFailureNotificationIconClassName() {
if (this.message && this.message.message_type === 'sms') {
return 'fa fa-mobile';
}
return this._super();
},
/**
* @override
*/
_computeFailureNotificationIconLabel() {
if (this.message && this.message.message_type === 'sms') {
return this.env._t("SMS");
}
return this._super();
},
/**
* @override
*/
_computeNotificationIconClassName() {
if (this.message && this.message.message_type === 'sms') {
return 'fa fa-mobile';
}
return this._super();
},
/**
* @override
*/
_computeNotificationIconLabel() {
if (this.message && this.message.message_type === 'sms') {
return this.env._t("SMS");
}
return this._super();
},
});
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-inherit="mail.Message" t-inherit-mode="extension">
<xpath expr="//*[@name='failureIcon']" position="replace">
<t t-if="messageView.message.message_type === 'snailmail'">
<i class="o_Message_notificationIcon fa fa-paper-plane"/>
</t>
<t t-else="">$0</t>
</xpath>
<xpath expr="//*[@name='notificationIcon']" position="replace">
<t t-if="messageView.message.message_type === 'snailmail'">
<i class="o_Message_notificationIcon fa fa-paper-plane"/>
</t>
<t t-else="">$0</t>
</xpath>
<!--
It was decided that the information displayed for snailmail messages
has to be different than for standard messages, see task-1907998.
-->
<xpath expr="//MessageNotificationPopoverContent" position="replace">
<t t-if="messageView.message.message_type === 'snailmail'">
<SnailmailNotificationPopover message="messageView.message"/>
</t>
<t t-else="">$0</t>
</xpath>
</t>
</templates>
@@ -4,7 +4,7 @@ import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
class SnailmailNotificationPopover extends Component {
export default class SnailmailNotificationPopoverContentView extends Component {
/**
* @returns {string}
@@ -39,10 +39,10 @@ class SnailmailNotificationPopover extends Component {
}
/**
* @returns {Message}
* @returns {SnailmailNotificationPopoverContentView}
*/
get message() {
return this.props.message;
get snailmailNotificationPopoverContentView() {
return this.props.record;
}
/**
@@ -50,16 +50,15 @@ class SnailmailNotificationPopover extends Component {
*/
get notification() {
// Messages from snailmail are considered to have at most one notification.
return this.message.notifications[0];
return this.snailmailNotificationPopoverContentView.message.notifications[0];
}
}
Object.assign(SnailmailNotificationPopover, {
props: { message: Object },
template: 'snailmail.SnailmailNotificationPopover',
Object.assign(SnailmailNotificationPopoverContentView, {
props: { record: Object },
template: 'snailmail.SnailmailNotificationPopoverContentView',
});
registerMessagingComponent(SnailmailNotificationPopover);
registerMessagingComponent(SnailmailNotificationPopoverContentView);
export default SnailmailNotificationPopover;
@@ -2,6 +2,6 @@
// Layout
// -----------------------------------------------------------------------------
.o_SnailmailNotificationPopover_icon {
.o_SnailmailNotificationPopoverContentView_icon {
margin-inline-end: map-get($spacers, 2);
}
@@ -1,15 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="snailmail.SnailmailNotificationPopover" owl="1">
<t t-if="message">
<div class="o_SnailmailNotificationPopover" t-attf-class="{{ className }}" t-ref="root">
<t t-if="notification">
<i class="o_SnailmailNotificationPopover_icon" t-att-class="iconClass" role="img"/>
<span t-esc="iconTitle"/>
</t>
</div>
</t>
<t t-name="snailmail.SnailmailNotificationPopoverContentView" owl="1">
<div class="o_SnailmailNotificationPopoverContentView m-2" t-attf-class="{{ className }}" t-ref="root">
<i class="o_SnailmailNotificationPopoverContentView_icon" t-att-class="iconClass" role="img"/>
<span t-esc="iconTitle"/>
</div>
</t>
</templates>
@@ -10,6 +10,10 @@ addFields('MessageView', {
inverse: 'messageViewOwnerAsSnailmailError',
isCausal: true,
}),
snailmailNotificationPopoverView: one('PopoverView', {
inverse: 'messageViewOwnerAsSnailmailNotificationContent',
isCausal: true,
}),
});
patchRecordMethods('MessageView', {
@@ -52,4 +56,29 @@ patchRecordMethods('MessageView', {
this._super(...arguments);
}
},
onClickNotificationIcon() {
if (this.message && this.message.message_type === 'snailmail') {
this.update({ snailmailNotificationPopoverView: {} });
return;
}
return this._super();
},
/**
* @override
*/
_computeFailureNotificationIconClassName() {
if (this.message && this.message.message_type === 'snailmail') {
return 'fa fa-paper-plane';
}
return this._super();
},
/**
* @override
*/
_computeNotificationIconClassName() {
if (this.message && this.message.message_type === 'snailmail') {
return 'fa fa-paper-plane';
}
return this._super();
},
});
@@ -0,0 +1,62 @@
/** @odoo-module **/
import { addFields, addRecordMethods, patchRecordMethods } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
import { clear } from '@mail/model/model_field_command';
// ensure that the model definition is loaded before the patch
import '@mail/models/popover_view';
addFields('PopoverView', {
messageViewOwnerAsSnailmailNotificationContent: one('MessageView', {
identifying: true,
inverse: 'snailmailNotificationPopoverView',
}),
snailmailNotificationPopoverContentView: one('SnailmailNotificationPopoverContentView', {
compute: '_computeSnailmailNotificationPopoverContentView',
inverse: 'popoverViewOwner',
isCausal: true,
}),
});
addRecordMethods('PopoverView', {
/**
* @private
* @returns {Object|FieldCommand}
*/
_computeSnailmailNotificationPopoverContentView() {
if (this.messageViewOwnerAsSnailmailNotificationContent) {
return {};
}
return clear();
},
});
patchRecordMethods('PopoverView', {
/**
* @override
*/
_computeAnchorRef() {
if (this.messageViewOwnerAsSnailmailNotificationContent) {
return this.messageViewOwnerAsSnailmailNotificationContent.notificationIconRef;
}
return this._super();
},
/**
* @override
*/
_computeContent() {
if (this.snailmailNotificationPopoverContentView) {
return this.snailmailNotificationPopoverContentView;
}
return this._super();
},
/**
* @override
*/
_computeContentComponentName() {
if (this.snailmailNotificationPopoverContentView) {
return 'SnailmailNotificationPopoverContentView';
}
return this._super();
},
});
@@ -0,0 +1,26 @@
/** @odoo-module **/
import { registerModel } from '@mail/model/model_core';
import { one } from '@mail/model/model_field';
registerModel({
name: 'SnailmailNotificationPopoverContentView',
recordMethods: {
/**
* @private
* @returns {Message}
*/
_computeMessage() {
return this.popoverViewOwner.messageViewOwnerAsSnailmailNotificationContent.message;
},
},
fields: {
message: one('Message', {
compute: '_computeMessage',
}),
popoverViewOwner: one('PopoverView', {
identifying: true,
inverse: 'snailmailNotificationPopoverContentView',
}),
},
});
@@ -64,21 +64,21 @@ QUnit.test('Sent', async function (assert) {
});
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover',
'.o_SnailmailNotificationPopoverContentView',
"notification popover should be open"
);
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover_icon',
'.o_SnailmailNotificationPopoverContentView_icon',
"popover should have one icon"
);
assert.hasClass(
document.querySelector('.o_SnailmailNotificationPopover_icon'),
document.querySelector('.o_SnailmailNotificationPopoverContentView_icon'),
'fa-check',
"popover should have the sent icon"
);
assert.strictEqual(
document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(),
document.querySelector('.o_SnailmailNotificationPopoverContentView').textContent.trim(),
"Sent",
"popover should have the sent text"
);
@@ -136,21 +136,21 @@ QUnit.test('Canceled', async function (assert) {
});
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover',
'.o_SnailmailNotificationPopoverContentView',
"notification popover should be open"
);
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover_icon',
'.o_SnailmailNotificationPopoverContentView_icon',
"popover should have one icon"
);
assert.hasClass(
document.querySelector('.o_SnailmailNotificationPopover_icon'),
document.querySelector('.o_SnailmailNotificationPopoverContentView_icon'),
'fa-trash-o',
"popover should have the canceled icon"
);
assert.strictEqual(
document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(),
document.querySelector('.o_SnailmailNotificationPopoverContentView').textContent.trim(),
"Canceled",
"popover should have the canceled text"
);
@@ -208,21 +208,21 @@ QUnit.test('Pending', async function (assert) {
});
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover',
'.o_SnailmailNotificationPopoverContentView',
"notification popover should be open"
);
assert.containsOnce(
document.body,
'.o_SnailmailNotificationPopover_icon',
'.o_SnailmailNotificationPopoverContentView_icon',
"popover should have one icon"
);
assert.hasClass(
document.querySelector('.o_SnailmailNotificationPopover_icon'),
document.querySelector('.o_SnailmailNotificationPopoverContentView_icon'),
'fa-clock-o',
"popover should have the pending icon"
);
assert.strictEqual(
document.querySelector('.o_SnailmailNotificationPopover').textContent.trim(),
document.querySelector('.o_SnailmailNotificationPopoverContentView').textContent.trim(),
"Awaiting Dispatch",
"popover should have the pending text"
);