[FIX] mail: show full company logo in avatar of message
Before this commit, avatar url of author of message always had style `.o_object_fit_cover`, which makes a nice crop when ratio of image does not match ratio of the `img`. When the author of message is a company, it shows the logo of the company, and it doesn't look nice to crop it. This commit fixes the issue by using `.o_object_fit_contain` for avatar of company, so that the logo is fully visible. When displaying the avatar of a partner whose `is_company` is undefined, the data is group-fetched, in order for models to eventually know the value of `is_company` and use the correct desirable showing. Task-3381748 closes odoo/odoo#133311 X-original-commit: 48194366f2364e2eb9d99dc4fb71e2de83d8f7a2 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -28,6 +28,7 @@ addModelNamesToFetch([
|
||||
|
||||
insertModelFields("res.partner", {
|
||||
description: { string: "description", type: "text" },
|
||||
is_company: { default: () => false },
|
||||
});
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -56,6 +56,7 @@ class TestImLivechatMessage(HttpCase):
|
||||
'attachment_ids': [],
|
||||
'author': {
|
||||
'id': self.users[1].partner_id.id,
|
||||
'is_company': self.users[1].partner_id.is_company,
|
||||
'name': "test1",
|
||||
},
|
||||
'body': message.body,
|
||||
|
||||
@@ -878,6 +878,7 @@ class Message(models.Model):
|
||||
message_sudo = self.browse(vals['id']).sudo().with_prefetch(self.ids)
|
||||
author = {
|
||||
'id': message_sudo.author_id.id,
|
||||
'is_company': message_sudo.author_id.is_company,
|
||||
'name': message_sudo.author_id.name,
|
||||
} if message_sudo.author_id else [('clear',)]
|
||||
guestAuthor = {
|
||||
|
||||
@@ -216,7 +216,7 @@ class Partner(models.Model):
|
||||
def mail_partner_format(self, fields=None):
|
||||
partners_format = dict()
|
||||
if not fields:
|
||||
fields = {'id': True, 'name': True, 'email': True, 'active': True, 'im_status': True, 'user': {}}
|
||||
fields = {'id': True, 'name': True, 'email': True, 'active': True, 'im_status': True, 'is_company': True, 'user': {}}
|
||||
for partner in self:
|
||||
data = {}
|
||||
if 'id' in fields:
|
||||
@@ -229,6 +229,8 @@ class Partner(models.Model):
|
||||
data['active'] = partner.active
|
||||
if 'im_status' in fields:
|
||||
data['im_status'] = partner.im_status
|
||||
if 'is_company' in fields:
|
||||
data['is_company'] = partner.is_company
|
||||
if 'user' in fields:
|
||||
internal_users = partner.user_ids - partner.user_ids.filtered('share')
|
||||
main_user = internal_users[0] if len(internal_users) > 0 else partner.user_ids[0] if len(partner.user_ids) > 0 else self.env['res.users']
|
||||
|
||||
@@ -164,6 +164,13 @@ export class Message extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
get authorAvatarAttClass() {
|
||||
return {
|
||||
o_object_fit_contain: this.props.message.author?.is_company,
|
||||
o_object_fit_cover: !this.props.message.author?.is_company,
|
||||
};
|
||||
}
|
||||
|
||||
get authorAvatarUrl() {
|
||||
if (
|
||||
this.message.type === "email" &&
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="o-mail-Message-core position-relative d-flex flex-shrink-0">
|
||||
<div class="o-mail-Message-sidebar d-flex flex-shrink-0" t-att-class="{ 'justify-content-end': isAlignedRight and !props.squashed, 'align-items-center justify-content-center': props.squashed }">
|
||||
<div t-if="!props.squashed" class="o-mail-Message-avatarContainer position-relative bg-view">
|
||||
<img class="o-mail-Message-avatar w-100 h-100 rounded o_object_fit_cover" t-att-src="authorAvatarUrl"/>
|
||||
<img class="o-mail-Message-avatar w-100 h-100 rounded" t-att-src="authorAvatarUrl" t-att-class="authorAvatarAttClass"/>
|
||||
</div>
|
||||
<t t-elif="!message.isTransient">
|
||||
<small t-if="isActive" class="o-mail-Message-date text-muted opacity-50 ms-2">
|
||||
|
||||
@@ -17,6 +17,8 @@ export class Persona extends Record {
|
||||
localId;
|
||||
/** @type {number} */
|
||||
id;
|
||||
/** @type {boolean | undefined} */
|
||||
is_company;
|
||||
/** @type {'partner' | 'guest'} */
|
||||
type;
|
||||
/** @type {string} */
|
||||
|
||||
@@ -4,6 +4,8 @@ import { Persona } from "@mail/core/common/persona_model";
|
||||
import { assignDefined, createLocalId, nullifyClearCommands } from "@mail/utils/common/misc";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useSequential } from "@mail/utils/common/hooks";
|
||||
import { markRaw } from "@odoo/owl";
|
||||
|
||||
export const DEFAULT_AVATAR = "/mail/static/src/img/smiley/avatar.jpg";
|
||||
|
||||
@@ -19,8 +21,15 @@ export class PersonaService {
|
||||
setup(env, services) {
|
||||
this.env = env;
|
||||
this.rpc = services.rpc;
|
||||
this.orm = services.orm;
|
||||
/** @type {import("@mail/core/common/store_service").Store} */
|
||||
this.store = services["mail.store"];
|
||||
this.sequential = useSequential();
|
||||
/** Queue used for handling sequential of fetching is_company of persona */
|
||||
this._sQueue = markRaw({
|
||||
/** @type {Set<number>} */
|
||||
todo: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
async updateGuestName(guest, name) {
|
||||
@@ -61,6 +70,44 @@ export class PersonaService {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchIsCompany(persona) {
|
||||
if (persona.type !== "partner") {
|
||||
// non-partner persona are always considered as not a company
|
||||
persona.is_company = false;
|
||||
return;
|
||||
}
|
||||
this._sQueue.todo.add(persona.id);
|
||||
await new Promise(setTimeout); // group synchronous request to fetch is_company
|
||||
await this.sequential(async () => {
|
||||
const ongoing = new Set();
|
||||
if (this._sQueue.todo.size === 0) {
|
||||
return;
|
||||
}
|
||||
// load 'todo' into 'ongoing'
|
||||
this._sQueue.todo.forEach((id) => ongoing.add(id));
|
||||
this._sQueue.todo.clear();
|
||||
// fetch is_company
|
||||
const partnerData = await this.orm.silent.read(
|
||||
"res.partner",
|
||||
[...ongoing],
|
||||
["is_company"],
|
||||
{
|
||||
context: { active_test: false },
|
||||
}
|
||||
);
|
||||
for (const { id, is_company } of partnerData) {
|
||||
this.insert({ id, is_company, type: "partner" });
|
||||
ongoing.delete(id);
|
||||
this._sQueue.todo.delete(id);
|
||||
}
|
||||
for (const id of ongoing) {
|
||||
// no is_company found => assumes persona is not a company
|
||||
this.insert({ id, is_company: false, type: "partner" });
|
||||
this._sQueue.todo.delete(id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List of known partner ids with a direct chat, ordered
|
||||
* by most recent interest (1st item being the most recent)
|
||||
@@ -87,7 +134,7 @@ export class PersonaService {
|
||||
}
|
||||
|
||||
export const personaService = {
|
||||
dependencies: ["rpc", "mail.store"],
|
||||
dependencies: ["orm", "rpc", "mail.store"],
|
||||
/**
|
||||
* @param {import("@web/env").OdooEnv} env
|
||||
* @param {Partial<import("services").Services>} services
|
||||
|
||||
@@ -50,6 +50,7 @@ export class ThreadService {
|
||||
this.router = services.router;
|
||||
this.ui = services.ui;
|
||||
this.messageService = services["mail.message"];
|
||||
this.personaService = services["mail.persona"];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1085,6 +1086,9 @@ export class ThreadService {
|
||||
if (!persona) {
|
||||
return DEFAULT_AVATAR;
|
||||
}
|
||||
if (persona.is_company === undefined) {
|
||||
this.personaService.fetchIsCompany(persona);
|
||||
}
|
||||
if (thread?.model === "discuss.channel") {
|
||||
if (persona.type === "partner") {
|
||||
return url(`/discuss/channel/${thread.id}/partner/${persona.id}/avatar_128`);
|
||||
@@ -1148,7 +1152,16 @@ export class ThreadService {
|
||||
}
|
||||
|
||||
export const threadService = {
|
||||
dependencies: ["mail.store", "orm", "rpc", "notification", "router", "mail.message", "ui"],
|
||||
dependencies: [
|
||||
"mail.store",
|
||||
"orm",
|
||||
"rpc",
|
||||
"notification",
|
||||
"router",
|
||||
"mail.message",
|
||||
"mail.persona",
|
||||
"ui",
|
||||
],
|
||||
/**
|
||||
* @param {import("@web/env").OdooEnv} env
|
||||
* @param {Partial<import("services").Services>} services
|
||||
|
||||
@@ -23,6 +23,12 @@ patch(Message.prototype, {
|
||||
this.userService = useService("user");
|
||||
this.messaging = useState(useService("mail.messaging"));
|
||||
},
|
||||
get authorAvatarAttClass() {
|
||||
return {
|
||||
...super.authorAvatarAttClass,
|
||||
"o_redirect cursor-pointer": this.hasAuthorClickable,
|
||||
};
|
||||
},
|
||||
getAuthorText() {
|
||||
return this.hasAuthorClickable() ? _t("Open profile") : undefined;
|
||||
},
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
<attribute name="t-on-click">ev => this.onClickAuthor(ev)</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//span[hasclass('o-mail-Message-author')]" position="attributes">
|
||||
<attribute name="t-att-class">{ 'cursor-pointer': hasAuthorClickable() }</attribute>
|
||||
<attribute name="t-att-title">getAuthorText()</attribute>
|
||||
<attribute name="t-on-click">ev => this.onClickAuthor(ev)</attribute>
|
||||
</xpath>
|
||||
|
||||
@@ -202,6 +202,7 @@ patch(MockServer.prototype, {
|
||||
});
|
||||
formattedAuthor = {
|
||||
id: author.id,
|
||||
is_company: author.is_company,
|
||||
name: author.name,
|
||||
};
|
||||
} else {
|
||||
|
||||
@@ -273,6 +273,7 @@ patch(MockServer.prototype, {
|
||||
email: partner.email,
|
||||
id: partner.id,
|
||||
im_status: partner.im_status,
|
||||
is_company: partner.is_company,
|
||||
name: partner.name,
|
||||
user: mainUser
|
||||
? {
|
||||
|
||||
@@ -146,6 +146,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -196,6 +197,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -246,6 +248,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -296,6 +299,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -346,6 +350,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -397,6 +402,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -417,6 +423,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': False,
|
||||
'id': self.users[12].partner_id.id,
|
||||
'im_status': 'offline',
|
||||
'is_company': False,
|
||||
'name': 'test12',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -483,6 +490,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -503,6 +511,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': False,
|
||||
'id': self.users[14].partner_id.id,
|
||||
'im_status': 'offline',
|
||||
'is_company': False,
|
||||
'name': 'test14',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -569,6 +578,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -589,6 +599,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': False,
|
||||
'id': self.users[15].partner_id.id,
|
||||
'im_status': 'offline',
|
||||
'is_company': False,
|
||||
'name': 'test15',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -655,6 +666,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -675,6 +687,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'test2@example.com',
|
||||
'id': self.users[2].partner_id.id,
|
||||
'im_status': 'offline',
|
||||
'is_company': False,
|
||||
'name': 'test2',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -741,6 +754,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -761,6 +775,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': False,
|
||||
'id': self.users[3].partner_id.id,
|
||||
'im_status': 'offline',
|
||||
'is_company': False,
|
||||
'name': 'test3',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
@@ -998,6 +1013,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'odoobot@example.com',
|
||||
'id': self.user_root.partner_id.id,
|
||||
'im_status': 'bot',
|
||||
'is_company': False,
|
||||
'name': 'OdooBot',
|
||||
'out_of_office_date_end': False,
|
||||
'user': [('clear',)],
|
||||
@@ -1008,6 +1024,7 @@ class TestDiscussFullPerformance(HttpCase):
|
||||
'email': 'e.e@example.com',
|
||||
'id': self.users[0].partner_id.id,
|
||||
'im_status': 'online',
|
||||
'is_company': False,
|
||||
'name': 'Ernest Employee',
|
||||
'out_of_office_date_end': False,
|
||||
'user': {
|
||||
|
||||
@@ -154,6 +154,10 @@ span.o_force_ltr {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.o_object_fit_contain {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.o_image_24_cover {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
Reference in New Issue
Block a user