diff --git a/addons/mail/static/src/core/thread_model.js b/addons/mail/static/src/core/thread_model.js index 0cc15614208..73c401792a2 100644 --- a/addons/mail/static/src/core/thread_model.js +++ b/addons/mail/static/src/core/thread_model.js @@ -378,7 +378,25 @@ export class Thread { orderedOnlineMembers.push(member); } } - return orderedOnlineMembers.sort((m1, m2) => (m1.persona.name < m2.persona.name ? -1 : 1)); + return orderedOnlineMembers.sort((m1, m2) => { + const m1HasRtc = Boolean(m1.rtcSession); + const m2HasRtc = Boolean(m2.rtcSession); + if (m1HasRtc === m2HasRtc) { + /** + * If raisingHand is falsy, it gets an Infinity value so that when + * we sort by [oldest/lowest-value]-first, falsy values end up last. + */ + const m1RaisingValue = m1.rtcSession?.raisingHand || Infinity; + const m2RaisingValue = m2.rtcSession?.raisingHand || Infinity; + if (m1HasRtc && m1RaisingValue !== m2RaisingValue) { + return m1RaisingValue - m2RaisingValue; + } else { + return m1.persona.name?.localeCompare(m2.persona.name) ?? 1; + } + } else { + return m2HasRtc - m1HasRtc; + } + }); } get unknownMembersCount() { diff --git a/addons/mail/static/src/discuss/channel_member_list.xml b/addons/mail/static/src/discuss/channel_member_list.xml index a6c08b0638a..621bf76810f 100644 --- a/addons/mail/static/src/discuss/channel_member_list.xml +++ b/addons/mail/static/src/discuss/channel_member_list.xml @@ -33,7 +33,13 @@ - + + + + + + + diff --git a/addons/mail/static/src/rtc/call.js b/addons/mail/static/src/rtc/call.js index 77534321e90..44e3c6b52fb 100644 --- a/addons/mail/static/src/rtc/call.js +++ b/addons/mail/static/src/rtc/call.js @@ -72,25 +72,45 @@ export class Call extends Component { } get visibleCards() { - const cards = []; + const raisingHandCards = []; + const sessionCards = []; + const invitationCards = []; const filterVideos = this.userSettings.showOnlyVideo && this.props.thread.videoCount > 0; for (const session of Object.values(this.props.thread.rtcSessions)) { if (!filterVideos || session.videoStream) { - cards.push({ + const data = { key: "session_" + session.id, session, - }); + }; + if (session.raisingHand) { + raisingHandCards.push(data); + } else { + sessionCards.push(data); + } } } if (!filterVideos) { for (const memberId of this.props.thread.invitedMemberIds) { - cards.push({ + invitationCards.push({ key: "member_" + memberId, member: this.store.channelMembers[memberId], }); } } - return cards; + raisingHandCards.sort((c1, c2) => { + return c1.session.raisingHand - c2.session.raisingHand; + }); + sessionCards.sort((c1, c2) => { + return ( + c1.session.channelMember?.persona?.name?.localeCompare( + c2.session.channelMember?.persona?.name + ) ?? 1 + ); + }); + invitationCards.sort((c1, c2) => { + return c1.member.persona?.name?.localeCompare(c2.member.persona?.name) ?? 1; + }); + return raisingHandCards.concat(sessionCards, invitationCards); } get visibleMainCards() { diff --git a/addons/mail/static/src/rtc/call_action_list.js b/addons/mail/static/src/rtc/call_action_list.js index 554bdcbd431..fa0ada2dc1e 100644 --- a/addons/mail/static/src/rtc/call_action_list.js +++ b/addons/mail/static/src/rtc/call_action_list.js @@ -36,7 +36,7 @@ export class CallActionList extends Component { } async onClickRaiseHand(ev) { - this.rtc.raiseHand(!this.rtc.state.selfSession.isRaisingHand); + this.rtc.raiseHand(!this.rtc.state.selfSession.raisingHand); } /** diff --git a/addons/mail/static/src/rtc/call_action_list.xml b/addons/mail/static/src/rtc/call_action_list.xml index 08ecc468c06..3a51cfa0b06 100644 --- a/addons/mail/static/src/rtc/call_action_list.xml +++ b/addons/mail/static/src/rtc/call_action_list.xml @@ -37,10 +37,10 @@ }"/> - Lower hand + Lower hand Raise hand diff --git a/addons/mail/static/src/rtc/call_participant_card.xml b/addons/mail/static/src/rtc/call_participant_card.xml index 4181584bd7c..6399b6806be 100644 --- a/addons/mail/static/src/rtc/call_participant_card.xml +++ b/addons/mail/static/src/rtc/call_participant_card.xml @@ -36,7 +36,7 @@ - + diff --git a/addons/mail/static/src/rtc/rtc_service.js b/addons/mail/static/src/rtc/rtc_service.js index bc3a13cfb95..f094877eb6c 100644 --- a/addons/mail/static/src/rtc/rtc_service.js +++ b/addons/mail/static/src/rtc/rtc_service.js @@ -440,10 +440,12 @@ export class Rtc { this.disconnect(session); break; case "raise_hand": - Object.assign(session, { isRaisingHand: payload.active }); + Object.assign(session, { + raisingHand: payload.active ? new Date() : undefined, + }); // eslint-disable-next-line no-case-declarations const notificationId = "raise_hand_" + session.id; - if (session.isRaisingHand) { + if (session.raisingHand) { this.addCallNotification({ id: notificationId, text: sprintf(_t("%s raised a hand"), session.name), @@ -691,7 +693,7 @@ export class Rtc { }, }); await this.notify([session], "raise_hand", { - active: this.state.selfSession.isRaisingHand, + active: Boolean(this.state.selfSession.raisingHand), }); } catch (e) { if (!(e instanceof DOMException) || e.name !== "OperationError") { @@ -929,10 +931,10 @@ export class Rtc { delete session.packetsSent; delete session.dtlsState; delete session.iceState; + delete session.raisingHand; delete session.logStep; delete session.audioError; delete session.videoError; - session.isRaisingHand = false; session.isTalking = false; this.removeVideoFromSession(session); session.dataChannel?.close(); @@ -1057,15 +1059,15 @@ export class Rtc { } /** - * @param {Boolean} isRaisingHand + * @param {Boolean} raise */ - async raiseHand(isRaisingHand) { + async raiseHand(raise) { if (!this.state.selfSession || !this.state.channel) { return; } - this.state.selfSession.isRaisingHand = isRaisingHand; + this.state.selfSession.raisingHand = raise ? new Date() : undefined; await this.notify(Object.values(this.state.channel.rtcSessions), "raise_hand", { - active: this.state.selfSession.isRaisingHand, + active: this.state.selfSession.raisingHand, }); } diff --git a/addons/mail/static/src/rtc/rtc_session_model.js b/addons/mail/static/src/rtc/rtc_session_model.js index ed4e237483d..d37a1b59e75 100644 --- a/addons/mail/static/src/rtc/rtc_session_model.js +++ b/addons/mail/static/src/rtc/rtc_session_model.js @@ -20,11 +20,12 @@ export class RtcSession { dataChannel; audioError; videoError; - isRaisingHand; isTalking; localVolume; /** @type {RTCPeerConnection} */ peerConnection; + /** @type {Date|undefined} */ + raisingHand; videoComponentCount = 0; /** @type {MediaStream} */ videoStream;