[IMP] mail: change the rtc layout system
This commit removes the layout menu, the layout is now automatically adjusted based on the focused tile and uses a collapsable sidebar. Layout variables are now located on the callView model instead of messaging, which means that the presence of video tiles may depend on multiple views with each their own layouts. It is therefore no longer possible to rely on the previously global layout state to know if a stream is viewed and should be downloaded. closes odoo/odoo#82692 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -1,31 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { useComponentToModel } from '@mail/component_hooks/use_component_to_model';
|
||||
import { registerMessagingComponent } from '@mail/utils/messaging_component';
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
export class CallLayoutMenu extends LegacyComponent {
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
setup() {
|
||||
super.setup();
|
||||
useComponentToModel({ fieldName: 'component' });
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {CallLayoutMenu}
|
||||
*/
|
||||
get callLayoutMenu() {
|
||||
return this.props.record;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign(CallLayoutMenu, {
|
||||
props: { record: Object },
|
||||
template: 'mail.CallLayoutMenu',
|
||||
});
|
||||
|
||||
registerMessagingComponent(CallLayoutMenu);
|
||||
@@ -1,128 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.CallLayoutMenu" owl="1">
|
||||
<t t-if="callLayoutMenu">
|
||||
<div class="o_CallLayoutMenu d-flex flex-column p-1" t-attf-class="{{ className }}" t-ref="root">
|
||||
<t t-if="callLayoutMenu.callView.threadView.thread.videoCount > 0">
|
||||
<div class="btn btn-light p-0 rounded font-weight-normal">
|
||||
<label class="o_CallLayoutMenu_radio d-flex align-items-center justify-content-between m-0 rounded fs-4 o_cursor_pointer">
|
||||
<div class="o_CallLayoutMenu_radioInputContainer">
|
||||
<input class="o_CallLayoutMenu_radioInput m-3" type="radio" name="filter" value="all" t-on-click="callLayoutMenu.onClickFilter" t-att-checked="!callLayoutMenu.callView.filterVideoGrid"/>
|
||||
<span class="o_CallLayoutMenu_inputText text-truncate">Show All</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn btn-light p-0 rounded font-weight-normal">
|
||||
<label class="o_CallLayoutMenu_radio d-flex align-items-center justify-content-between m-0 rounded fs-4 o_cursor_pointer">
|
||||
<div class="o_CallLayoutMenu_radioInputContainer">
|
||||
<input class="o_CallLayoutMenu_radioInput m-3" type="radio" name="filter" value="video" t-on-click="callLayoutMenu.onClickFilter" t-att-checked="callLayoutMenu.callView.filterVideoGrid"/>
|
||||
<span class="o_CallLayoutMenu_inputText text-truncate">Show only video</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<hr class="o_CallLayoutMenu_separator w-100 bg-300"/>
|
||||
</t>
|
||||
<div class="btn btn-light p-0 rounded font-weight-normal">
|
||||
<label class="o_CallLayoutMenu_radio d-flex align-items-center justify-content-between m-0 rounded fs-4 o_cursor_pointer">
|
||||
<div class="o_CallLayoutMenu_radioInputContainer">
|
||||
<input class="o_CallLayoutMenu_radioInput m-3" type="radio" name="layout" value="tiled" t-on-click="callLayoutMenu.onClickLayout" t-att-checked="messaging.userSetting.rtcLayout === 'tiled'"/>
|
||||
<span class="o_CallLayoutMenu_inputText text-truncate">Tiled</span>
|
||||
</div>
|
||||
<span class="o_CallLayoutMenu_radioIcon me-1">
|
||||
<t t-call="mail.CallLayoutMenu_gridSvg"/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn btn-light p-0 rounded font-weight-normal">
|
||||
<label class="o_CallLayoutMenu_radio d-flex align-items-center justify-content-between m-0 rounded fs-4 o_cursor_pointer">
|
||||
<div class="o_CallLayoutMenu_radioInputContainer">
|
||||
<input class="o_CallLayoutMenu_radioInput m-3" type="radio" name="layout" value="spotlight" t-on-click="callLayoutMenu.onClickLayout" t-att-checked="messaging.userSetting.rtcLayout === 'spotlight'"/>
|
||||
<span class="o_CallLayoutMenu_inputText text-truncate">Spotlight</span>
|
||||
</div>
|
||||
<span class="o_CallLayoutMenu_radioIcon me-1">
|
||||
<t t-call="mail.CallLayoutMenu_spotlightSvg"/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn btn-light p-0 rounded font-weight-normal">
|
||||
<label class="o_CallLayoutMenu_radio d-flex align-items-center justify-content-between m-0 rounded fs-4 o_cursor_pointer">
|
||||
<div class="o_CallLayoutMenu_radioInputContainer">
|
||||
<input class="o_CallLayoutMenu_radioInput m-3" type="radio" name="layout" value="sidebar" t-on-click="callLayoutMenu.onClickLayout" t-att-checked="messaging.userSetting.rtcLayout === 'sidebar'"/>
|
||||
<span class="o_CallLayoutMenu_inputText text-truncate">Sidebar</span>
|
||||
</div>
|
||||
<span class="o_CallLayoutMenu_radioIcon me-1">
|
||||
<t t-call="mail.CallLayoutMenu_sidebarSvg"/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- TODO TEMPORARY ICONS -->
|
||||
|
||||
<t t-name="mail.CallLayoutMenu_gridSvg" owl="1">
|
||||
<svg viewBox="0 0 300 300" width="5rem" height="5rem">
|
||||
<g transform="matrix(1.08 0 0 1.29 63.85 96.61)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 150 96.61)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 236 96.61)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 63.85 150)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 63.85 204)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 150 150)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 150 204)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 236 150)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(1.08 0 0 1.29 236 204)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
</svg>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.CallLayoutMenu_spotlightSvg" owl="1">
|
||||
<svg viewBox="0 0 300 300" width="5rem" height="5rem">
|
||||
<g transform="matrix(3.38 0 0 4.04 150 150)">
|
||||
<rect style="fill: currentColor" x="-38" y="-20" rx="0" ry="0" width="75" height="39"/>
|
||||
</g>
|
||||
</svg>
|
||||
</t>
|
||||
|
||||
<t t-name="mail.CallLayoutMenu_sidebarSvg" owl="1">
|
||||
<svg viewBox="0 0 300 300" width="5rem" height="5rem">
|
||||
<g transform="matrix(2.66 0 0 4.04 122.89 150.02)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(0.63 0 0 0.77 252.6 150.07)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(0.63 0 0 0.77 252.6 118.07)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(0.63 0 0 0.77 252.6 86.07)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(0.63 0 0 0.77 252.6 182.07)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
<g transform="matrix(0.63 0 0 0.77 252.6 214.07)">
|
||||
<rect style="fill: currentColor" x="-37.46" y="-19.415" rx="0" ry="0" width="74.92" height="38.83"/>
|
||||
</g>
|
||||
</svg>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -49,3 +49,7 @@
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_CallOptionMenu_separator {
|
||||
background-color: $border-color;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,15 @@
|
||||
<t t-name="mail.CallOptionMenu" owl="1">
|
||||
<t t-if="callOptionMenu">
|
||||
<div class="o_CallOptionMenu" t-attf-class="{{ className }}" t-ref="root">
|
||||
<button class="o_CallOptionMenu_button" t-on-click="callOptionMenu.onClickLayout">
|
||||
<i class="o_CallOptionMenu_buttonIcon fa fa-lg fa-th-large"/>
|
||||
<span class="o_CallOptionMenu_buttonText">Change layout</span>
|
||||
</button>
|
||||
<t t-if="callOptionMenu.callActionListView.callView.threadView.thread.videoCount > 0">
|
||||
<div class="o_CallOptionMenu_button">
|
||||
<label class="o_CallOptionMenu_checkBoxLabel o_cursor_pointer d-flex align-items-center p-1 mb-0">
|
||||
<input class="o_CallOptionMenu_checkBox p-1 ml-2 mr-2" type="checkbox" t-on-change="callOptionMenu.onChangeVideoFilterCheckbox" t-att-checked="callOptionMenu.callActionListView.callView.filterVideoGrid"/>
|
||||
<span class="o_CallOptionMenu_buttonText">Video only</span>
|
||||
</label>
|
||||
</div>
|
||||
<hr class="o_CallOptionMenu_separator w-100"/>
|
||||
</t>
|
||||
<t t-if="callOptionMenu.callActionListView.callView.isFullScreen">
|
||||
<button class="o_CallOptionMenu_button" t-on-click="callOptionMenu.onClickDeactivateFullScreen">
|
||||
<i class="o_CallOptionMenu_buttonIcon fa fa-lg fa-compress"/>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
max-height: #{"min(100%, 100px)"}; // interpolated as not supported by Sass
|
||||
max-width: #{"min(100%, 100px)"};
|
||||
aspect-ratio: 1;
|
||||
border: solid $gray-700;
|
||||
border: solid $gray-500;
|
||||
|
||||
&.o-isTalking {
|
||||
border: solid darken($o-enterprise-primary-color, 5%);
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
<t t-if="callParticipantCard">
|
||||
<div class="o_CallParticipantCard position-relative d-flex flex-column align-items-center justify-content-center mh-100 mw-100 p-1 rounded-sm"
|
||||
t-att-class="{
|
||||
'o-isClickable o_cursor_pointer': callParticipantCard.invitedGuest or callParticipantCard.invitedPartner or !callParticipantCard.isMinimized,
|
||||
'o-isTalking': !callParticipantCard.isMinimized and callParticipantCard.isTalking,
|
||||
'o-isInvitation opacity-50': callParticipantCard.isInvitation,
|
||||
}"
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_CallView_mainParticipantContainer {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.o_CallView_grid {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
@@ -40,6 +44,13 @@
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
|
||||
.o_CallView_sidebarButton {
|
||||
font-size: 1.2rem;
|
||||
padding: map-get($spacers, 2);
|
||||
top: 48%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
@@ -53,3 +64,14 @@
|
||||
background: gray('700');
|
||||
}
|
||||
}
|
||||
|
||||
.o_CallView_mainParticipantContainer {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.o_CallView_sidebarButton {
|
||||
color: white;
|
||||
border-radius: 10px 0px 0px 10px;
|
||||
border-right: 0px;
|
||||
background-color: lighten(black, 10%);
|
||||
}
|
||||
|
||||
@@ -9,20 +9,22 @@
|
||||
|
||||
<!-- Call members display -->
|
||||
<div class="o_CallView_participantContainer d-flex justify-content-between w-100 h-100 overflow-hidden" t-on-click="callView.onClick" t-on-mousemove="callView.onMouseMove">
|
||||
<t t-if="callView.layout !== 'tiled'">
|
||||
<div class="o_CallView_mainParticipantContainer d-flex flex-grow-1 justify-content-center mw-100 mh-100">
|
||||
<t t-if="callView.mainParticipantCard">
|
||||
<CallParticipantCard className="'o_CallView_participantCard'" record="callView.mainParticipantCard"/>
|
||||
<t t-if="callView.mainParticipantCard">
|
||||
<div class="o_CallView_mainParticipantContainer d-flex flex-grow-1 justify-content-center position-relative mw-100 mh-100">
|
||||
<CallParticipantCard className="'o_CallView_participantCard'" record="callView.mainParticipantCard"/>
|
||||
<t t-if="callView.showOverlay">
|
||||
<i t-if="callView.hasSidebar" class="o_CallView_sidebarButton position-absolute fa fa-arrow-right" title="Hide sidebar" t-on-click="callView.onClickHideSidebar"/>
|
||||
<i t-else="" class="o_CallView_sidebarButton position-absolute fa fa-arrow-left" title="Show sidebar" t-on-click="callView.onClickShowSidebar"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="callView.layout !== 'spotlight'">
|
||||
<t t-if="callView.tileParticipantCards.length > 0">
|
||||
<div
|
||||
class="o_CallView_grid d-flex align-items-center h-100"
|
||||
t-att-class="{
|
||||
'o-isTiled flex-wrap justify-content-center w-100': callView.layout === 'tiled',
|
||||
'flex-column': callView.layout !== 'tiled',
|
||||
'o-isSidebar': callView.layout === 'sidebar',
|
||||
'o-isTiled flex-wrap justify-content-center w-100': !callView.mainParticipantCard,
|
||||
'flex-column': callView.mainParticipantCard,
|
||||
'o-isSidebar': callView.hasSidebar and callView.mainParticipantCard,
|
||||
}"
|
||||
t-ref="tileContainer"
|
||||
t-attf-style="--height:{{state.tileHeight}}px; --width:{{state.tileWidth}}px; --columns:{{state.columnCount}};"
|
||||
@@ -32,7 +34,7 @@
|
||||
<!-- maybe filter focused partner out? -->
|
||||
<CallParticipantCard
|
||||
className="'o_CallView_participantCard o_CallView_gridTile'"
|
||||
classNameObj="{ 'w-100' : callView.layout === 'sidebar' }"
|
||||
classNameObj="{ 'w-100' : callView.hasSidebar and callView.mainParticipantCard }"
|
||||
record="participantCard"
|
||||
/>
|
||||
</t>
|
||||
@@ -59,14 +61,6 @@
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
<t t-if="callView.layoutMenu">
|
||||
<Dialog size="'small'" title="callView.layoutSettingsTitle" onClosed="callView.onLayoutSettingsDialogClosed">
|
||||
<CallLayoutMenu record="callView.layoutMenu"/>
|
||||
<t t-set-slot="buttons">
|
||||
<!-- Explicit No buttons -->
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'CallLayoutMenu',
|
||||
identifyingFields: ['callView'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickFilter(ev) {
|
||||
ev.preventDefault();
|
||||
switch (ev.target.value) {
|
||||
case 'all':
|
||||
this.callView.update({
|
||||
filterVideoGrid: false,
|
||||
});
|
||||
break;
|
||||
case 'video':
|
||||
this.callView.update({
|
||||
filterVideoGrid: true,
|
||||
});
|
||||
if (this.messaging.focusedRtcSession && !this.messaging.focusedRtcSession.videoStream) {
|
||||
this.messaging.update({ focusedRtcSession: clear() });
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickLayout(ev) {
|
||||
ev.preventDefault();
|
||||
this.messaging.userSetting.update({
|
||||
rtcLayout: ev.target.value,
|
||||
});
|
||||
this.component.trigger('dialog-closed');
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
component: attr(),
|
||||
callView: one('CallView', {
|
||||
inverse: 'layoutMenu',
|
||||
readonly: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
@@ -2,11 +2,23 @@
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear } from '@mail/model/model_field_command';
|
||||
|
||||
registerModel({
|
||||
name: 'CallOptionMenu',
|
||||
identifyingFields: ['callActionListView'],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {Event} ev
|
||||
*/
|
||||
onChangeVideoFilterCheckbox(ev) {
|
||||
const filterVideoGrid = ev.target.checked;
|
||||
const activeRtcSession = this.callActionListView.callView.activeRtcSession;
|
||||
if (filterVideoGrid && activeRtcSession && !activeRtcSession.videoStream) {
|
||||
this.callActionListView.callView.update({ activeRtcSession: clear() });
|
||||
}
|
||||
this.callActionListView.callView.update({ filterVideoGrid });
|
||||
},
|
||||
/**
|
||||
* Creates and download a file that contains the logs of the current RTC call.
|
||||
*
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { clear, insertAndReplace } from '@mail/model/model_field_command';
|
||||
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
|
||||
import { isEventHandled, markEventHandled } from '@mail/utils/utils';
|
||||
|
||||
import { sprintf } from '@web/core/utils/strings';
|
||||
|
||||
registerModel({
|
||||
name: 'CallParticipantCard',
|
||||
identifyingFields: ['relationalId'],
|
||||
identifyingFields: [['rtcSession', 'invitedPartner', 'invitedGuest'], ['callViewOfMainCard', 'callViewOfTile']],
|
||||
recordMethods: {
|
||||
/**
|
||||
* @param {Event} ev
|
||||
@@ -24,9 +24,11 @@ registerModel({
|
||||
if (isEventHandled(ev, 'CallParticipantCard.clickVolumeAnchor')) {
|
||||
return;
|
||||
}
|
||||
if (!this.invitedPartner && !this.invitedGuest) {
|
||||
if (!this.isMinimized) {
|
||||
this.messaging.toggleFocusedRtcSession(this.rtcSession.id);
|
||||
if (this.rtcSession) {
|
||||
if (this.callView.activeRtcSession === this.rtcSession && this.callViewOfMainCard) {
|
||||
this.callView.update({ activeRtcSession: clear() });
|
||||
} else {
|
||||
this.callView.update({ activeRtcSession: replace(this.rtcSession) });
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -83,6 +85,18 @@ registerModel({
|
||||
return `/mail/channel/${this.channel.id}/guest/${this.invitedGuest.id}/avatar_128?unique=${this.invitedGuest.name}`;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {mail.callView}
|
||||
*/
|
||||
_computeCallView() {
|
||||
const callView = this.callViewOfMainCard || this.callViewOfTile;
|
||||
if (callView) {
|
||||
return replace(callView);
|
||||
} else {
|
||||
return clear();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
@@ -111,8 +125,7 @@ registerModel({
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeIsMinimized() {
|
||||
const callView = this.callViewOfMainCard || this.callViewOfTile;
|
||||
return Boolean(callView && callView.isMinimized);
|
||||
return Boolean(this.callView && this.callView.isMinimized);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -198,11 +211,15 @@ registerModel({
|
||||
/**
|
||||
* If set, this card represents an invitation of this guest to this call.
|
||||
*/
|
||||
invitedGuest: one('Guest'),
|
||||
invitedGuest: one('Guest', {
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* If set, this card represents an invitation of this partner to this call.
|
||||
*/
|
||||
invitedPartner: one('Partner'),
|
||||
invitedPartner: one('Partner', {
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* States whether this card is representing a person with a pending
|
||||
* invitation.
|
||||
@@ -238,28 +255,33 @@ registerModel({
|
||||
compute: '_computeOutboundConnectionTypeText',
|
||||
}),
|
||||
/**
|
||||
* Unique id for this session provided when instantiated.
|
||||
* The callView that displays this card.
|
||||
*/
|
||||
relationalId: attr({
|
||||
readonly: true,
|
||||
required: true,
|
||||
callView: one('CallView', {
|
||||
compute: '_computeCallView',
|
||||
inverse: 'participantCards',
|
||||
}),
|
||||
/**
|
||||
* The call view for which this card is the spotlight.
|
||||
*/
|
||||
callViewOfMainCard: one('CallView', {
|
||||
inverse: 'mainParticipantCard',
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* The call view for which this card is one of the tiles.
|
||||
*/
|
||||
callViewOfTile: one('CallView', {
|
||||
inverse: 'tileParticipantCards',
|
||||
readonly: true,
|
||||
}),
|
||||
/**
|
||||
* If set, this card represents a rtcSession.
|
||||
*/
|
||||
rtcSession: one('RtcSession'),
|
||||
rtcSession: one('RtcSession', {
|
||||
inverse: 'callParticipantCards',
|
||||
readonly: true,
|
||||
}),
|
||||
callParticipantVideoView: one('CallParticipantVideoView', {
|
||||
compute: '_computeCallParticipantVideoView',
|
||||
inverse: 'callParticipantCardOwner',
|
||||
|
||||
@@ -31,8 +31,14 @@ registerModel({
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onLayoutSettingsDialogClosed(ev) {
|
||||
this.toggleLayoutMenu();
|
||||
onClickHideSidebar(ev) {
|
||||
this.update({ hasSidebar: false });
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
onClickShowSidebar(ev) {
|
||||
this.update({ hasSidebar: true });
|
||||
},
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
@@ -116,13 +122,6 @@ registerModel({
|
||||
this.update({ isFullScreen: false });
|
||||
}
|
||||
},
|
||||
toggleLayoutMenu() {
|
||||
if (!this.layoutMenu) {
|
||||
this.update({ layoutMenu: insertAndReplace() });
|
||||
return;
|
||||
}
|
||||
this.update({ layoutMenu: clear() });
|
||||
},
|
||||
//----------------------------------------------------------------------
|
||||
// Private
|
||||
//----------------------------------------------------------------------
|
||||
@@ -140,10 +139,7 @@ registerModel({
|
||||
* @private
|
||||
*/
|
||||
_computeIsControllerFloating() {
|
||||
return (
|
||||
this.isFullScreen ||
|
||||
this.layout !== "tiled" && !this.threadView.compact
|
||||
);
|
||||
return Boolean(this.isFullScreen || this.activeRtcSession && !this.threadView.compact);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -155,36 +151,11 @@ registerModel({
|
||||
if (this.isFullScreen || this.threadView.compact) {
|
||||
return false;
|
||||
}
|
||||
if (this.mainParticipantCard) {
|
||||
return false;
|
||||
}
|
||||
return !this.threadView.thread.rtc || this.threadView.thread.videoCount === 0;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_computeLayout() {
|
||||
if (!this.threadView) {
|
||||
return 'tiled';
|
||||
}
|
||||
if (this.isMinimized) {
|
||||
return 'tiled';
|
||||
}
|
||||
if (!this.threadView.thread.rtc) {
|
||||
return 'tiled';
|
||||
}
|
||||
if (!this.mainParticipantCard) {
|
||||
return 'tiled';
|
||||
}
|
||||
if (
|
||||
this.threadView.thread.rtcSessions.length +
|
||||
this.threadView.thread.invitedPartners.length +
|
||||
this.threadView.thread.invitedGuests.length < 2
|
||||
) {
|
||||
return "tiled";
|
||||
}
|
||||
if (this.threadView.compact && this.messaging.userSetting.rtcLayout === 'sidebar') {
|
||||
return 'spotlight';
|
||||
}
|
||||
return this.messaging.userSetting.rtcLayout;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {string}
|
||||
@@ -196,17 +167,13 @@ registerModel({
|
||||
* @private
|
||||
*/
|
||||
_computeMainParticipantCard() {
|
||||
if (!this.messaging || !this.threadView) {
|
||||
if (!this.messaging || !this.activeRtcSession || !this.threadView || !this.activeRtcSession) {
|
||||
return clear();
|
||||
}
|
||||
if (this.messaging.focusedRtcSession && this.messaging.focusedRtcSession.channel === this.threadView.thread) {
|
||||
return insert({
|
||||
relationalId: `rtc_session_${this.messaging.focusedRtcSession.localId}_${this.threadView.thread.localId}`,
|
||||
rtcSession: replace(this.messaging.focusedRtcSession),
|
||||
channel: replace(this.threadView.thread),
|
||||
});
|
||||
}
|
||||
return clear();
|
||||
return insert({
|
||||
rtcSession: replace(this.activeRtcSession),
|
||||
channel: replace(this.threadView.thread),
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -222,14 +189,19 @@ registerModel({
|
||||
if (!this.threadView) {
|
||||
return clear();
|
||||
}
|
||||
if (this.activeRtcSession && !this.hasSidebar) {
|
||||
return clear();
|
||||
}
|
||||
const tileCards = [];
|
||||
const sessionPartners = new Set();
|
||||
const sessionGuests = new Set();
|
||||
for (const rtcSession of this.threadView.thread.rtcSessions) {
|
||||
if (this.filterVideoGrid && !rtcSession.videoStream) {
|
||||
continue;
|
||||
}
|
||||
rtcSession.partner && sessionPartners.add(rtcSession.partner.id);
|
||||
rtcSession.guest && sessionPartners.add(rtcSession.guest.id);
|
||||
tileCards.push({
|
||||
relationalId: `rtc_session_${rtcSession.localId}_${this.threadView.thread.localId}`,
|
||||
rtcSession: replace(rtcSession),
|
||||
channel: replace(this.threadView.thread),
|
||||
});
|
||||
@@ -239,7 +211,6 @@ registerModel({
|
||||
continue;
|
||||
}
|
||||
tileCards.push({
|
||||
relationalId: `invited_partner_${partner.localId}_${this.threadView.thread.localId}`,
|
||||
invitedPartner: replace(partner),
|
||||
channel: replace(this.threadView.thread),
|
||||
});
|
||||
@@ -249,7 +220,6 @@ registerModel({
|
||||
continue;
|
||||
}
|
||||
tileCards.push({
|
||||
relationalId: `invited_guest_${guest.localId}_${this.threadView.thread.localId}`,
|
||||
invitedGuest: replace(guest),
|
||||
channel: replace(this.threadView.thread),
|
||||
});
|
||||
@@ -307,6 +277,10 @@ registerModel({
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
* The rtc session that is the focus/spotlight of the viewer.
|
||||
*/
|
||||
activeRtcSession: one('RtcSession'),
|
||||
/**
|
||||
* The aspect ratio of the tiles.
|
||||
*/
|
||||
@@ -320,6 +294,12 @@ registerModel({
|
||||
filterVideoGrid: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determines if the viewer should have a sidebar.
|
||||
*/
|
||||
hasSidebar: attr({
|
||||
default: true,
|
||||
}),
|
||||
/**
|
||||
* Determines if the controller is an overlay or a bottom bar.
|
||||
*/
|
||||
@@ -341,13 +321,6 @@ registerModel({
|
||||
default: false,
|
||||
compute: '_computeIsMinimized',
|
||||
}),
|
||||
/**
|
||||
* Determines the layout use for the tiling of the participant cards.
|
||||
*/
|
||||
layout: attr({
|
||||
default: 'tiled',
|
||||
compute: '_computeLayout',
|
||||
}),
|
||||
/**
|
||||
* Text content that is displayed on title of the layout settings dialog.
|
||||
*/
|
||||
@@ -360,6 +333,15 @@ registerModel({
|
||||
mainParticipantCard: one('CallParticipantCard', {
|
||||
compute: '_computeMainParticipantCard',
|
||||
inverse: 'callViewOfMainCard',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* All the participant cards of the call viewer (main card and tile cards).
|
||||
* this is a technical inverse to distinguish from the other relation 'tileParticipantCards'.
|
||||
*/
|
||||
participantCards: many('CallParticipantCard', {
|
||||
inverse: 'callView',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* The model for the controller (buttons).
|
||||
@@ -370,13 +352,6 @@ registerModel({
|
||||
inverse: 'callView',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* The model for the menu to control the layout of the viewer.
|
||||
*/
|
||||
layoutMenu: one('CallLayoutMenu', {
|
||||
inverse: 'callView',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Text content that is displayed on title of the settings dialog.
|
||||
*/
|
||||
@@ -404,6 +379,7 @@ registerModel({
|
||||
tileParticipantCards: many('CallParticipantCard', {
|
||||
compute: '_computeTileParticipantCards',
|
||||
inverse: 'callViewOfTile',
|
||||
isCausal: true,
|
||||
}),
|
||||
},
|
||||
onChanges: [
|
||||
|
||||
@@ -221,24 +221,6 @@ registerModel({
|
||||
}, { shadow: true });
|
||||
this.models['Partner'].insert(dataList);
|
||||
},
|
||||
/**
|
||||
* @param {String} sessionId
|
||||
*/
|
||||
toggleFocusedRtcSession(sessionId) {
|
||||
const rtcSession = this.messaging.models['RtcSession'].findFromIdentifyingData({
|
||||
id: sessionId,
|
||||
});
|
||||
const focusedSessionId = this.focusedRtcSession && this.focusedRtcSession.id;
|
||||
if (!sessionId || focusedSessionId === sessionId) {
|
||||
this.update({ focusedRtcSession: clear() });
|
||||
return;
|
||||
}
|
||||
this.update({ focusedRtcSession: replace(rtcSession) });
|
||||
if (this.userSetting.rtcLayout !== 'tiled') {
|
||||
return;
|
||||
}
|
||||
this.userSetting.update({ rtcLayout: 'sidebar' });
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Object} browser
|
||||
@@ -319,12 +301,6 @@ registerModel({
|
||||
this.soundEffects.incomingCall.stop();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeFocusedRtcSession() {
|
||||
this.rtc.filterIncomingVideoTraffic(this.focusedRtcSession && [this.focusedRtcSession.peerToken]);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
@@ -388,7 +364,6 @@ registerModel({
|
||||
fetchImStatusTimerDuration: attr({
|
||||
default: 50 * 1000,
|
||||
}),
|
||||
focusedRtcSession: one('RtcSession'),
|
||||
/**
|
||||
* Mailbox History.
|
||||
*/
|
||||
@@ -505,9 +480,5 @@ registerModel({
|
||||
dependencies: ['ringingThreads'],
|
||||
methodName: '_onChangeRingingThreads',
|
||||
}),
|
||||
new OnChange({
|
||||
dependencies: ['focusedRtcSession'],
|
||||
methodName: '_onChangeFocusedRtcSession',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -56,27 +56,6 @@ registerModel({
|
||||
this.channel.endCall();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {array} [allowedTokens] tokens of the peerConnections for which
|
||||
* the incoming video traffic is allowed. If undefined, all traffic is
|
||||
* allowed.
|
||||
*/
|
||||
filterIncomingVideoTraffic(allowedTokens) {
|
||||
const tokenSet = new Set(allowedTokens);
|
||||
for (const rtcSession of this.connectedRtcSessions) {
|
||||
const fullDirection = this.videoTrack ? 'sendrecv' : 'recvonly';
|
||||
const limitedDirection = this.videoTrack ? 'sendonly' : 'inactive';
|
||||
const transceiver = rtcSession.rtcPeerConnection.getTransceiver('video')
|
||||
if (!transceiver) {
|
||||
continue;
|
||||
}
|
||||
if (!tokenSet.size || tokenSet.has(rtcSession.id)) {
|
||||
transceiver.direction = fullDirection;
|
||||
} else {
|
||||
transceiver.direction = limitedDirection;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {number} sender id of the session that sent the notification
|
||||
* @param {String} content JSON
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, one } from '@mail/model/model_field';
|
||||
import { OnChange } from '@mail/model/model_onchange';
|
||||
|
||||
registerModel({
|
||||
name: 'RtcPeerConnection',
|
||||
@@ -20,8 +21,38 @@ registerModel({
|
||||
const transceivers = this.peerConnection.getTransceivers();
|
||||
return transceivers[this.messaging.rtc.orderedTransceiverNames.indexOf(trackKind)];
|
||||
},
|
||||
/**
|
||||
* The download is allowed when there are views that display the video stream.
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_computeAcceptsVideoStream() {
|
||||
return Boolean(this.rtcSession.callParticipantCards && this.rtcSession.callParticipantCards.length > 0);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onChangeAcceptsVideoStream() {
|
||||
const transceiver = this.getTransceiver('video');
|
||||
if (!transceiver) {
|
||||
return;
|
||||
}
|
||||
const rtc = this.rtcSession.rtcAsConnectedSession;
|
||||
if (this.acceptsVideoStream) {
|
||||
transceiver.direction = rtc.videoTrack ? 'sendrecv' : 'recvonly';
|
||||
} else {
|
||||
transceiver.direction = rtc.videoTrack ? 'sendonly' : 'inactive';
|
||||
}
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
/**
|
||||
* Determines whether the video stream receiver accepts video stream download.
|
||||
*/
|
||||
acceptsVideoStream: attr({
|
||||
compute: '_computeAcceptsVideoStream',
|
||||
}),
|
||||
/**
|
||||
* Contains the browser.RTCPeerConnection instance of this RTC Session.
|
||||
* If unset, this RTC Session is not considered as connected
|
||||
@@ -33,4 +64,10 @@ registerModel({
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
onChanges: [
|
||||
new OnChange({
|
||||
dependencies: ['acceptsVideoStream'],
|
||||
methodName: '_onChangeAcceptsVideoStream',
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -198,11 +198,9 @@ registerModel({
|
||||
return;
|
||||
}
|
||||
const track = trackKind === 'audio' ? this.rtcAsConnectedSession.audioTrack : this.rtcAsConnectedSession.videoTrack;
|
||||
const fullDirection = track ? 'sendrecv' : 'recvonly';
|
||||
const limitedDirection = track ? 'sendonly' : 'inactive';
|
||||
let transceiverDirection = fullDirection;
|
||||
if (trackKind === 'video') {
|
||||
transceiverDirection = !this.messaging.focusedRtcSession || this.messaging.focusedRtcSession === this ? fullDirection : limitedDirection;
|
||||
let transceiverDirection = track ? 'sendrecv' : 'recvonly';
|
||||
if (trackKind === 'video' && !this.rtcPeerConnection.acceptsVideoStream) {
|
||||
transceiverDirection = track ? 'sendonly' : 'inactive';
|
||||
}
|
||||
let transceiver;
|
||||
if (initTransceiver) {
|
||||
@@ -432,6 +430,14 @@ registerModel({
|
||||
calledChannels: many('Thread', {
|
||||
inverse: 'rtcInvitingSession',
|
||||
}),
|
||||
/**
|
||||
* The participant cards of this session,
|
||||
* this is used to know how many views are displaying this session.
|
||||
*/
|
||||
callParticipantCards: many('CallParticipantCard', {
|
||||
inverse: 'rtcSession',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* States whether there is currently an error with the audio element.
|
||||
*/
|
||||
|
||||
@@ -280,13 +280,6 @@ registerModel({
|
||||
inverse: 'userSetting',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* layout of the rtc session display chosen by the user
|
||||
* possible values: tiled, spotlight, sidebar
|
||||
*/
|
||||
rtcLayout: attr({
|
||||
default: 'tiled',
|
||||
}),
|
||||
/**
|
||||
* true if the user wants to use push to talk (over voice activation)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user