diff --git a/addons/mail/static/src/components/call_participant_card/call_participant_card.xml b/addons/mail/static/src/components/call_participant_card/call_participant_card.xml index 4886d9c8620..01d7544511e 100644 --- a/addons/mail/static/src/components/call_participant_card/call_participant_card.xml +++ b/addons/mail/static/src/components/call_participant_card/call_participant_card.xml @@ -6,10 +6,10 @@
@@ -37,7 +37,7 @@ - + diff --git a/addons/mail/static/src/models/call_participant_card.js b/addons/mail/static/src/models/call_participant_card.js index 54054dda97d..0863d89e03e 100644 --- a/addons/mail/static/src/models/call_participant_card.js +++ b/addons/mail/static/src/models/call_participant_card.js @@ -9,7 +9,7 @@ import { sprintf } from '@web/core/utils/strings'; registerModel({ name: 'CallParticipantCard', - identifyingFields: [['rtcSession', 'invitedMember'], ['callViewAsMainCard', 'callViewAsTile']], + identifyingFields: ['channelMember', ['callViewAsMainCard', 'callViewAsTile']], recordMethods: { /** * @param {Event} ev @@ -32,12 +32,12 @@ registerModel({ } return; } - const channel = this.channel; + const channel = this.channelMember.channel.thread; const channelData = await this.messaging.rpc(({ route: '/mail/rtc/channel/cancel_call_invitation', params: { - channel_id: this.channel.id, - member_ids: this.invitedMember && [this.invitedMember.id], + channel_id: channel.id, + member_ids: [this.channelMember.id], }, })); if (!channel.exists()) { @@ -66,21 +66,6 @@ registerModel({ } this.volumeMenuAnchorRef.el.click(); }, - /** - * @private - * @returns {string} - */ - _computeAvatarUrl() { - if (!this.channel) { - return; - } - if (this.rtcSession) { - return this.rtcSession.channelMember.avatarUrl; - } - if (this.invitedMember) { - return this.invitedMember.avatarUrl; - } - }, /** * @private * @returns {mail.callView} @@ -98,7 +83,7 @@ registerModel({ * @returns {boolean} */ _computeHasConnectionInfo() { - return Boolean(this.rtcSession && this.env.debug && this.channel.rtc); + return Boolean(this.rtcSession && this.env.debug && this.channelMember.channel.thread.rtc); }, /** * @private @@ -106,12 +91,12 @@ registerModel({ */ _computeInboundConnectionTypeText() { if (!this.rtcSession || !this.rtcSession.remoteCandidateType) { - return sprintf(this.env._t('From %s: no connection'), this.name); + return sprintf(this.env._t('From %s: no connection'), this.channelMember.persona.name); } return sprintf( this.env._t('From %(name)s: %(candidateType)s (%(protocol)s)'), { candidateType: this.rtcSession.remoteCandidateType, - name: this.name, + name: this.channelMember.persona.name, protocol: this.messaging.rtc.protocolsByCandidateTypes[this.rtcSession.remoteCandidateType], }, ); @@ -130,30 +115,18 @@ registerModel({ _computeIsTalking() { return Boolean(this.rtcSession && this.rtcSession.isTalking && !this.rtcSession.isMute); }, - /** - * @private - * @returns {string} - */ - _computeName() { - if (this.rtcSession) { - return this.rtcSession.channelMember.persona.name; - } - if (this.invitedMember) { - return this.invitedMember.name; - } - }, /** * @private * @returns {string} */ _computeOutboundConnectionTypeText() { if (!this.rtcSession || !this.rtcSession.localCandidateType) { - return sprintf(this.env._t('To %s: no connection'), this.name); + return sprintf(this.env._t('To %s: no connection'), this.channelMember.persona.name); } return sprintf( this.env._t('To %(name)s: %(candidateType)s (%(protocol)s)'), { candidateType: this.rtcSession.localCandidateType, - name: this.name, + name: this.channelMember.persona.name, protocol: this.messaging.rtc.protocolsByCandidateTypes[this.rtcSession.localCandidateType], }, ); @@ -170,17 +143,10 @@ registerModel({ }, }, fields: { - /** - * The relative url of the image that represents the card. - */ - avatarUrl: attr({ - compute: '_computeAvatarUrl', - }), - /** - * The channel of the call. - */ - channel: one('Thread', { + channelMember: one('ChannelMember', { + inverse: 'callParticipantCards', required: true, + readonly: true, }), /** * Determines whether or not we show the connection info. @@ -194,9 +160,6 @@ registerModel({ inboundConnectionTypeText: attr({ compute: '_computeInboundConnectionTypeText', }), - invitedMember: one('ChannelMember', { - readonly: true, - }), /** * Determines if this card has to be displayed in a minimized form. */ @@ -211,13 +174,6 @@ registerModel({ default: false, compute: '_computeIsTalking', }), - /** - * The name of the rtcSession or the invited partner. - */ - name: attr({ - default: 'Anonymous', - compute: '_computeName', - }), /** * The text describing the outbound ice connection candidate type. */ @@ -245,12 +201,9 @@ registerModel({ inverse: 'tileParticipantCards', readonly: true, }), - /** - * If set, this card represents a rtcSession. - */ rtcSession: one('RtcSession', { + related: 'channelMember.rtcSession', inverse: 'callParticipantCards', - readonly: true, }), callParticipantVideoView: one('CallParticipantVideoView', { compute: '_computeCallParticipantVideoView', diff --git a/addons/mail/static/src/models/call_view.js b/addons/mail/static/src/models/call_view.js index 9e6d48904a1..4c6936fe888 100644 --- a/addons/mail/static/src/models/call_view.js +++ b/addons/mail/static/src/models/call_view.js @@ -115,8 +115,7 @@ registerModel({ return clear(); } return insert({ - rtcSession: replace(this.activeRtcSession), - channel: replace(this.channel), + channelMember: replace(this.activeRtcSession.channelMember), }); }, /** @@ -128,6 +127,7 @@ registerModel({ }, /** * @private + * @returns {FieldCommand} */ _computeTileParticipantCards() { if (!this.threadView) { @@ -137,19 +137,12 @@ registerModel({ return clear(); } const tileCards = []; - for (const rtcSession of this.channel.rtcSessions) { - if (this.filterVideoGrid && !rtcSession.videoStream) { + for (const channelMember of this.channel.channel.callParticipants) { + if (this.filterVideoGrid && !channelMember.isStreaming) { continue; } tileCards.push({ - rtcSession: replace(rtcSession), - channel: replace(this.channel), - }); - } - for (const member of this.channel.invitedMembers) { - tileCards.push({ - invitedMember: replace(member), - channel: replace(this.channel), + channelMember: replace(channelMember), }); } return insertAndReplace(tileCards); diff --git a/addons/mail/static/src/models/channel.js b/addons/mail/static/src/models/channel.js index 198ed402af1..a4d39bed99e 100644 --- a/addons/mail/static/src/models/channel.js +++ b/addons/mail/static/src/models/channel.js @@ -2,7 +2,7 @@ import { registerModel } from '@mail/model/model_core'; import { attr, one, many } from '@mail/model/model_field'; -import { insertAndReplace } from '@mail/model/model_field_command'; +import { insertAndReplace, replace } from '@mail/model/model_field_command'; registerModel({ name: 'Channel', @@ -29,6 +29,17 @@ registerModel({ _computeAreAllMembersLoaded() { return this.memberCount === this.channelMembers.length; }, + /** + * @private + * @returns {FieldCommand} + */ + _computeCallParticipants() { + const callParticipants = this.thread.invitedMembers; + for (const rtcSession of this.thread.rtcSessions) { + callParticipants.push(rtcSession.channelMember); + } + return replace(callParticipants); + }, /** * @private * @returns {FieldCommand} @@ -46,6 +57,16 @@ registerModel({ _computeUnknownMemberCount() { return this.memberCount - this.channelMembers.length; }, + /** + * @private + * @returns {Array[]} + */ + _sortCallParticipants() { + return [ + ['truthy-first', 'rtcSession'], + ['smaller-first', 'rtcSession.id'], + ]; + }, /** * @private * @returns {Array[]} @@ -61,6 +82,10 @@ registerModel({ areAllMembersLoaded: attr({ compute: '_computeAreAllMembersLoaded', }), + callParticipants: many('ChannelMember', { + compute: '_computeCallParticipants', + sort: '_sortCallParticipants', + }), channelMembers: many('ChannelMember', { inverse: 'channel', isCausal: true, diff --git a/addons/mail/static/src/models/channel_member.js b/addons/mail/static/src/models/channel_member.js index 63f79e0ee91..e76c9497ece 100644 --- a/addons/mail/static/src/models/channel_member.js +++ b/addons/mail/static/src/models/channel_member.js @@ -47,11 +47,22 @@ registerModel({ } return clear(); }, + /** + * @private + * @returns {boolean} + */ + _computeIsStreaming() { + return Boolean(this.rtcSession && this.rtcSession.videoStream); + }, }, fields: { avatarUrl: attr({ compute: '_computeAvatarUrl', }), + callParticipantCards: many('CallParticipantCard', { + inverse: 'channelMember', + isCausal: true, + }), channel: one('Channel', { inverse: 'channelMembers', readonly: true, @@ -73,10 +84,16 @@ registerModel({ readonly: true, required: true, }), + isStreaming: attr({ + compute: '_computeIsStreaming', + }), persona: one('Persona', { inverse: 'channelMembers', readonly: true, required: true, }), + rtcSession: one('RtcSession', { + inverse: 'channelMember', + }), }, }); diff --git a/addons/mail/static/src/models/rtc_session.js b/addons/mail/static/src/models/rtc_session.js index 7cfaa1bfc05..e453885806b 100644 --- a/addons/mail/static/src/models/rtc_session.js +++ b/addons/mail/static/src/models/rtc_session.js @@ -365,7 +365,9 @@ registerModel({ channel: one('Thread', { inverse: 'rtcSessions', }), - channelMember: one('ChannelMember'), + channelMember: one('ChannelMember', { + inverse: 'rtcSession', + }), connectionRecoveryTimeout: attr(), /** * State of the connection with this session, uses RTCPeerConnection.iceConnectionState