[REF] mail: move CallView's main display to its own model/component

part of task-2692836

closes odoo/odoo#94541

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Thanh Dodeur
2022-06-29 14:44:26 +02:00
parent 321fc728de
commit b52c966ff3
13 changed files with 412 additions and 323 deletions
@@ -5,7 +5,7 @@
<t t-if="callActionListView">
<div class="o_CallActionList d-flex justify-content-between" t-attf-class="{{ className }}" t-ref="root">
<div class="o_CallActionList_buttons d-flex align-items-center flex-wrap">
<t t-if="callActionListView.callView.threadView.thread.rtc and messaging.rtc.currentRtcSession">
<t t-if="callActionListView.channel.rtc and messaging.rtc.currentRtcSession">
<button class="o_CallActionList_button btn d-flex m-1 border-0 rounded-circle bg-800 shadow-none"
t-att-class="{ 'o-isActive': !messaging.rtc.currentRtcSession.isMute, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
t-att-aria-label="callActionListView.microphoneButtonTitle"
@@ -75,25 +75,25 @@
</t>
</Popover>
</t>
<t t-if="!callActionListView.callView.threadView.thread.rtc">
<t t-if="!callActionListView.channel">
<button class="o_CallActionList_button o_CallActionList_callToggle btn btn-success d-flex m-1 border-0 rounded-circle shadow-none"
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
aria-label="Join Video Call"
title="Join Video Call"
t-att-disabled="callActionListView.callView.threadView.thread.hasPendingRtcRequest"
t-att-disabled="callActionListView.channel.hasPendingRtcRequest"
t-on-click="callActionListView.onClickToggleVideoCall">
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
<i class="fa fa-video-camera fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
</div>
</button>
</t>
<t t-if="callActionListView.callView.threadView.thread">
<t t-if="callActionListView.callView.threadView.thread.rtcInvitingSession and !callActionListView.callView.threadView.thread.rtc">
<t t-if="callActionListView.channel">
<t t-if="callActionListView.channel.rtcInvitingSession and !callActionListView.channel.rtc">
<button class="o_CallActionList_button o_CallActionList_callToggle o-isActive btn btn-danger d-flex m-1 border-0 rounded-circle shadow-none"
t-att-class="{ 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall }"
aria-label="Reject"
title="Reject"
t-att-disabled="callActionListView.callView.threadView.thread.hasPendingRtcRequest"
t-att-disabled="callActionListView.channel.hasPendingRtcRequest"
t-on-click="callActionListView.onClickRejectCall">
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
<i class="fa fa-phone fa-stack-1x" t-att-class="{ 'fa-lg': !callActionListView.isSmall }"/>
@@ -102,8 +102,8 @@
</t>
<button class="o_CallActionList_button o_CallActionList_callToggle btn d-flex m-1 border-0 rounded-circle shadow-none"
t-att-aria-label="callActionListView.callButtonTitle"
t-att-class="{ 'o-isActive btn-danger': !!callActionListView.callView.threadView.thread.rtc, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall, 'btn-success': !callActionListView.callView.threadView.thread.rtc }"
t-att-disabled="callActionListView.callView.threadView.thread.hasPendingRtcRequest"
t-att-class="{ 'o-isActive btn-danger': !!callActionListView.channel.rtc, 'o-isSmall p-2': callActionListView.isSmall, 'p-3': !callActionListView.isSmall, 'btn-success': !callActionListView.channel.rtc }"
t-att-disabled="callActionListView.channel.hasPendingRtcRequest"
t-att-title="callActionListView.callButtonTitle"
t-on-click="callActionListView.onClickToggleAudioCall">
<div class="o_CallActionList_buttonIconWrapper fa-stack" t-att-class="{ 'o-isSmall': callActionListView.isSmall }">
@@ -0,0 +1,73 @@
/** @odoo-module **/
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { useUpdate } from '@mail/component_hooks/use_update';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class CallMainView extends Component {
/**
* @override
*/
setup() {
super.setup();
useRefToModel({ fieldName: 'tileContainerRef', refName: 'tileContainer', });
useUpdate({ func: () => this._update() });
}
//--------------------------------------------------------------------------
// Getters / Setters
//--------------------------------------------------------------------------
/**
* @returns {CallMainView}
*/
get callMainView() {
return this.props.record;
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_setTileLayout() {
if (!this.root.el) {
return;
}
if (!this.callMainView.tileContainerRef.el) {
return;
}
const { width, height } = this.callMainView.tileContainerRef.el.getBoundingClientRect();
const { tileWidth, tileHeight } = this.callMainView.calculateTessellation({
aspectRatio: this.callMainView.callView.aspectRatio,
containerHeight: height,
containerWidth: width,
tileCount: this.callMainView.tileContainerRef.el.children.length,
});
this.callMainView.update({
tileHeight,
tileWidth,
});
}
/**
* @private
*/
_update() {
this._setTileLayout();
}
}
Object.assign(CallMainView, {
props: { record: Object },
template: 'mail.CallMainView',
});
registerMessagingComponent(CallMainView);
@@ -0,0 +1,42 @@
// ------------------------------------------------------------------
// Layout
// ------------------------------------------------------------------
.o_CallMainView {
min-width: 0;
}
.o_CallMainView_mainParticipantContainer {
min-width: 0;
}
.o_CallMainView_gridParticipantCard {
width: var(--width);
height: var(--height);
min-width: var(--width);
min-height: var(--height);
aspect-ratio: 16/9;
}
.o_CallMainView_sidebarButton {
font-size: 1.2rem;
padding: map-get($spacers, 2);
top: 50%;
transform: translateY(-50%);
right: 0;
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.o_CallMainView_mainParticipantContainer {
background-color: black;
}
.o_CallMainView_sidebarButton {
color: white;
border-radius: 10px 0px 0px 10px;
border-right: 0px;
background-color: lighten(black, 10%);
}
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.CallMainView" owl="1">
<t t-if="callMainView">
<div class="o_CallMainView d-flex flex-grow-1 flex-column align-items-center justify-content-center" t-ref="root" t-on-mouseleave="callMainView.onMouseleave">
<t t-set="dummy" t-value="messaging and messaging.device and messaging.device.globalWindowInnerHeight and messaging.device.globalWindowInnerWidth"/>
<div class="o_CallMainView_participantContainer d-flex justify-content-between w-100 h-100 overflow-hidden" t-on-click="callMainView.onClick" t-on-mousemove="callMainView.onMouseMove">
<t t-if="callMainView.callView.mainParticipantCard">
<div class="o_CallMainView_mainParticipantContainer d-flex flex-grow-1 justify-content-center position-relative mw-100 mh-100">
<CallParticipantCard className="'o_CallMainView_participantCard'" record="callMainView.callView.mainParticipantCard"/>
<t t-if="callMainView.showOverlay and !callMainView.callView.threadView.compact">
<i t-if="callMainView.callView.hasSidebar" class="o_CallMainView_sidebarButton o_cursor_pointer position-absolute fa fa-arrow-right" title="Hide sidebar" t-on-click="callMainView.onClickHideSidebar"/>
<i t-else="" class="o_CallMainView_sidebarButton o_cursor_pointer position-absolute fa fa-arrow-left" title="Show sidebar" t-on-click="callMainView.onClickShowSidebar"/>
</t>
</div>
</t>
<t t-elif="callMainView.callView.tileParticipantCards.length > 0">
<div
class="o_CallMainView_grid d-flex align-items-center overflow-hidden h-100 w-100 flex-wrap justify-content-center"
t-ref="tileContainer"
t-attf-style="--height:{{callMainView.tileHeight}}px; --width:{{callMainView.tileWidth}}px;"
>
<t t-foreach="callMainView.callView.tileParticipantCards" t-as="participantCard" t-key="'grid_tile_'+participantCard.localId">
<CallParticipantCard
className="'o_CallMainView_gridParticipantCard'"
record="participantCard"
/>
</t>
</div>
</t>
</div>
<!-- Buttons -->
<t t-if="callMainView.showOverlay or !callMainView.isControllerFloating">
<div class="o_CallMainView_controls d-flex justify-content-center w-100 pb-1" t-att-class="{ 'o-isFloating position-absolute bottom-0 pb-3': callMainView.isControllerFloating }">
<div class="o_CallMainView_controlsOverlayContainer" t-on-mousemove="callMainView.onMouseMoveOverlay">
<CallActionList record="callMainView.callActionListView"/>
</div>
</div>
</t>
</div>
</t>
</t>
</templates>
@@ -4,16 +4,16 @@
<t t-name="mail.CallOptionMenu" owl="1">
<t t-if="callOptionMenu">
<div class="o_CallOptionMenu d-flex flex-column" t-attf-class="{{ className }}" t-ref="root">
<t t-if="callOptionMenu.callActionListView.callView.threadView.thread.videoCount > 0">
<t t-if="callOptionMenu.callActionListView.channel.videoCount > 0">
<div class="o_CallOptionMenu_button btn d-flex align-items-center border-0 rounded">
<label class="o_CallOptionMenu_checkBoxLabel o_cursor_pointer d-flex align-items-center mb-0 text-800 font-weight-normal">
<input class="o_CallOptionMenu_checkBox mx-2 p-1" type="checkbox" t-on-change="callOptionMenu.onChangeVideoFilterCheckbox" t-att-checked="callOptionMenu.callActionListView.callView.filterVideoGrid"/>
<input class="o_CallOptionMenu_checkBox mx-2 p-1" type="checkbox" t-on-change="callOptionMenu.onChangeVideoFilterCheckbox" t-att-checked="callOptionMenu.callView.filterVideoGrid"/>
<span class="o_CallOptionMenu_buttonText py-1 pl-1 text-truncate">Video only</span>
</label>
</div>
<hr class="o_CallOptionMenu_separator w-100 my-1 bg-300"/>
</t>
<t t-if="callOptionMenu.callActionListView.callView.isFullScreen">
<t t-if="callOptionMenu.callView.isFullScreen">
<button class="o_CallOptionMenu_button btn d-flex align-items-center border-0 rounded text-800 font-weight-normal" t-on-click="callOptionMenu.onClickDeactivateFullScreen">
<i class="o_CallOptionMenu_buttonIcon fa fa-lg fa-compress m-1 p-1"/>
<span class="o_CallOptionMenu_buttonText text-truncate">Exit full screen</span>
@@ -1,7 +1,5 @@
/** @odoo-module **/
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { useUpdate } from '@mail/component_hooks/use_update';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
import Dialog from 'web.OwlDialog';
@@ -16,15 +14,6 @@ const components = {
export class CallView extends Component {
/**
* @override
*/
setup() {
super.setup();
useRefToModel({ fieldName: 'tileContainerRef', refName: 'tileContainer', });
useUpdate({ func: () => this._update() });
}
//--------------------------------------------------------------------------
// Getters / Setters
//--------------------------------------------------------------------------
@@ -36,42 +25,6 @@ export class CallView extends Component {
return this.props.record;
}
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_setTileLayout() {
if (!this.root.el) {
return;
}
if (!this.callView.tileContainerRef.el) {
return;
}
const { width, height } = this.callView.tileContainerRef.el.getBoundingClientRect();
const { tileWidth, tileHeight } = this.callView.calculateTessellation({
aspectRatio: this.callView.aspectRatio,
containerHeight: height,
containerWidth: width,
tileCount: this.callView.tileContainerRef.el.children.length,
});
this.callView.update({
tileHeight,
tileWidth,
});
}
/**
* @private
*/
_update() {
this._setTileLayout();
}
}
Object.assign(CallView, {
@@ -10,42 +10,3 @@
min-height: 20%;
}
}
.o_CallView_mainView {
min-width: 0;
}
.o_CallView_mainParticipantContainer {
min-width: 0;
}
.o_CallView_gridParticipantCard {
width: var(--width);
height: var(--height);
min-width: var(--width);
min-height: var(--height);
aspect-ratio: 16/9;
}
.o_CallView_sidebarButton {
font-size: 1.2rem;
padding: map-get($spacers, 2);
top: 50%;
transform: translateY(-50%);
right: 0;
}
// ------------------------------------------------------------------
// Style
// ------------------------------------------------------------------
.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%);
}
@@ -3,49 +3,12 @@
<t t-name="mail.CallView" owl="1">
<t t-if="callView">
<div class="o_CallView d-flex bg-dark" t-att-class="{'o-fullScreen fixed-top vw-100 vh-100': callView.isFullScreen, 'o-isMinimized': callView.isMinimized, 'h-50': !callView.isMinimized and !callView.isFullScreen, 'position-relative': !callView.isFullScreen }" t-attf-class="{{ className }}" t-on-mouseleave="callView.onMouseleave" t-ref="root">
<div class="o_CallView d-flex bg-dark" t-att-class="{'o-fullScreen fixed-top vw-100 vh-100': callView.isFullScreen, 'o-isMinimized': callView.isMinimized, 'h-50': !callView.isMinimized and !callView.isFullScreen, 'position-relative': !callView.isFullScreen }" t-attf-class="{{ className }}" t-ref="root">
<!-- Used to make the component depend on the window size and trigger an update when the window size changes. -->
<t t-set="dummy" t-value="messaging and messaging.device and messaging.device.globalWindowInnerHeight and messaging.device.globalWindowInnerWidth"/>
<div class="o_CallView_mainView d-flex flex-grow-1 flex-column align-items-center justify-content-center">
<!-- 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.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 and !callView.threadView.compact">
<i t-if="callView.hasSidebar" class="o_CallView_sidebarButton o_cursor_pointer position-absolute fa fa-arrow-right" title="Hide sidebar" t-on-click="callView.onClickHideSidebar"/>
<i t-else="" class="o_CallView_sidebarButton o_cursor_pointer position-absolute fa fa-arrow-left" title="Show sidebar" t-on-click="callView.onClickShowSidebar"/>
</t>
</div>
</t>
<t t-elif="callView.tileParticipantCards.length > 0">
<div
class="o_CallView_grid d-flex align-items-center overflow-hidden h-100 w-100 flex-wrap justify-content-center"
t-ref="tileContainer"
t-attf-style="--height:{{callView.tileHeight}}px; --width:{{callView.tileWidth}}px;"
>
<t t-foreach="callView.tileParticipantCards" t-as="participantCard" t-key="'grid_tile_'+participantCard.localId">
<CallParticipantCard
className="'o_CallView_gridParticipantCard'"
record="participantCard"
/>
</t>
</div>
</t>
</div>
<!-- Buttons -->
<t t-if="callView.showOverlay or !callView.isControllerFloating">
<div class="o_CallView_controls d-flex justify-content-center w-100 pb-1" t-att-class="{ 'o-isFloating position-absolute bottom-0 pb-3': callView.isControllerFloating }">
<div class="o_CallView_controlsOverlayContainer" t-on-mousemove="callView.onMouseMoveOverlay">
<CallActionList record="callView.callActionListView"/>
</div>
</div>
</t>
</div>
<CallMainView record="callView.callMainView"/>
<CallSidebarView t-if="callView.callSidebarView" record="callView.callSidebarView"/>
<!-- Dialogs -->
<t t-if="messaging.userSetting.callSettingsMenu.isOpen">
<Dialog size="'small'" title="callView.settingsTitle" onClosed="callView.onRtcSettingsDialogClosed">
@@ -6,7 +6,7 @@ import { clear, insertAndReplace } from '@mail/model/model_field_command';
registerModel({
name: 'CallActionListView',
identifyingFields: ['callView'],
identifyingFields: ['callMainView'],
recordMethods: {
/**
* @param {MouseEvent} ev
@@ -43,10 +43,10 @@ registerModel({
* @param {MouseEvent} ev
*/
async onClickRejectCall(ev) {
if (this.callView.threadView.thread.hasPendingRtcRequest) {
if (this.channel.hasPendingRtcRequest) {
return;
}
await this.callView.threadView.thread.leaveCall();
await this.channel.leaveCall();
},
/**
* @param {MouseEvent} ev
@@ -58,19 +58,19 @@ registerModel({
* @param {MouseEvent} ev
*/
async onClickToggleAudioCall(ev) {
if (this.callView.threadView.thread.hasPendingRtcRequest) {
if (this.channel.hasPendingRtcRequest) {
return;
}
await this.callView.threadView.thread.toggleCall();
await this.channel.toggleCall();
},
/**
* @param {MouseEvent} ev
*/
async onClickToggleVideoCall(ev) {
if (this.callView.threadView.thread.hasPendingRtcRequest) {
if (this.channel.hasPendingRtcRequest) {
return;
}
await this.callView.threadView.thread.toggleCall({
await this.channel.toggleCall({
startWithVideo: true,
});
},
@@ -79,10 +79,10 @@ registerModel({
* @returns {string|FieldCommand}
*/
_computeCallButtonTitle() {
if (!this.callView.threadView.thread) {
if (!this.channel) {
return clear();
}
if (this.callView.threadView.thread.rtc) {
if (this.channel.rtc) {
return this.env._t("Disconnect");
} else {
return this.env._t("Join Call");
@@ -149,15 +149,23 @@ registerModel({
compute: '_computeCallButtonTitle',
default: '',
}),
callView: one('CallView', {
callMainView: one('CallMainView', {
inverse: 'callActionListView',
readonly: true,
required: true,
}),
callView: one('CallView', {
related: 'callMainView.callView',
required: true,
}),
cameraButtonTitle: attr({
compute: '_computeCameraButtonTitle',
default: '',
}),
channel: one('Thread', {
related: 'callMainView.channel',
required: true,
}),
headphoneButtonTitle: attr({
compute: '_computeHeadphoneButtonTitle',
default: '',
@@ -0,0 +1,182 @@
/** @odoo-module **/
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 { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'CallMainView',
identifyingFields: ['callView'],
recordMethods: {
/**
* Finds a tile layout and dimensions that respects param0.aspectRatio while maximizing
* the total area covered by the tiles within the specified container dimensions.
*
* @param {Object} param0
* @param {number} [param0.aspectRatio]
* @param {number} param0.containerHeight
* @param {number} param0.containerWidth
* @param {number} param0.tileCount
*/
calculateTessellation({ aspectRatio = 1, containerHeight, containerWidth, tileCount }) {
let optimalLayout = {
area: 0,
cols: 0,
tileHeight: 0,
tileWidth: 0,
};
for (let columnCount = 1; columnCount <= tileCount; columnCount++) {
const rowCount = Math.ceil(tileCount / columnCount);
const potentialHeight = containerWidth / (columnCount * aspectRatio);
const potentialWidth = containerHeight / rowCount;
let tileHeight;
let tileWidth;
if (potentialHeight > potentialWidth) {
tileHeight = Math.floor(potentialWidth);
tileWidth = Math.floor(tileHeight * aspectRatio);
} else {
tileWidth = Math.floor(containerWidth / columnCount);
tileHeight = Math.floor(tileWidth / aspectRatio);
}
const area = tileHeight * tileWidth;
if (area <= optimalLayout.area) {
continue;
}
optimalLayout = {
area,
tileHeight,
tileWidth,
};
}
return optimalLayout;
},
/**
* @param {MouseEvent} ev
*/
onClick(ev) {
this._showOverlay();
},
/**
* @param {MouseEvent} ev
*/
onClickHideSidebar(ev) {
this.callView.update({ hasSidebar: false });
},
/**
* @param {MouseEvent} ev
*/
onClickShowSidebar(ev) {
this.callView.update({ hasSidebar: true });
},
/**
* @param {MouseEvent} ev
*/
onMouseleave(ev) {
if (ev.relatedTarget && ev.relatedTarget.closest('.o_CallActionList_popover')) {
// the overlay should not be hidden when the cursor leaves to enter the controller popover
return;
}
if (!this.exists()) {
return;
}
this.update({ showOverlay: false });
},
/**
* @param {MouseEvent} ev
*/
onMouseMove(ev) {
if (!this.exists()) {
return;
}
if (isEventHandled(ev, 'CallMainView.MouseMoveOverlay')) {
return;
}
this._showOverlay();
},
/**
* @param {MouseEvent} ev
*/
onMouseMoveOverlay(ev) {
if (!this.exists()) {
return;
}
markEventHandled(ev, 'CallMainView.MouseMoveOverlay');
this.update({
showOverlay: true,
showOverlayTimer: clear(),
});
},
onShowOverlayTimeout() {
this.update({ showOverlay: false });
},
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
/**
* @private
*/
_computeIsControllerFloating() {
return Boolean(this.callView.isFullScreen || this.callView.activeRtcSession && !this.callView.threadView.compact);
},
/**
* Shows the overlay (buttons) for a set a mount of time.
*
* @private
*/
_showOverlay() {
this.update({
showOverlay: true,
showOverlayTimer: [clear(), insertAndReplace()],
});
},
},
fields: {
/**
* The model for the controller (buttons).
*/
callActionListView: one('CallActionListView', {
default: insertAndReplace(),
inverse: 'callMainView',
isCausal: true,
readonly: true,
}),
callView: one('CallView', {
inverse: 'callMainView',
required: true,
readonly: true,
}),
channel: one('Thread', {
related: 'callView.channel',
required: true,
}),
/**
* Determines if the controller is an overlay or a bottom bar.
*/
isControllerFloating: attr({
default: false,
compute: '_computeIsControllerFloating',
}),
/**
* Determines if we show the overlay with the control buttons.
*/
showOverlay: attr({
default: true,
}),
showOverlayTimer: one('Timer', {
inverse: 'callMainViewAsShowOverlay',
isCausal: true,
}),
tileContainerRef: attr(),
tileHeight: attr({
default: 0,
}),
tileWidth: attr({
default: 0,
}),
},
});
@@ -13,11 +13,11 @@ registerModel({
*/
onChangeVideoFilterCheckbox(ev) {
const filterVideoGrid = ev.target.checked;
const activeRtcSession = this.callActionListView.callView.activeRtcSession;
const activeRtcSession = this.callView.activeRtcSession;
if (filterVideoGrid && activeRtcSession && !activeRtcSession.videoStream) {
this.callActionListView.callView.update({ activeRtcSession: clear() });
this.callView.update({ activeRtcSession: clear() });
}
this.callActionListView.callView.update({ filterVideoGrid });
this.callView.update({ filterVideoGrid });
},
/**
* Creates and download a file that contains the logs of the current RTC call.
@@ -25,7 +25,7 @@ registerModel({
* @param {MouseEvent} ev
*/
async onClickDownloadLogs(ev) {
const channel = this.callActionListView.callView.threadView.thread;
const channel = this.callActionListView.channel;
if (!channel.rtc) {
return;
}
@@ -43,14 +43,14 @@ registerModel({
* @param {MouseEvent} ev
*/
onClickActivateFullScreen(ev) {
this.callActionListView.callView.activateFullScreen();
this.callView.activateFullScreen();
this.component.trigger('o-popover-close');
},
/**
* @param {MouseEvent} ev
*/
onClickDeactivateFullScreen(ev) {
this.callActionListView.callView.deactivateFullScreen();
this.callView.deactivateFullScreen();
this.component.trigger('o-popover-close');
},
/**
@@ -71,5 +71,9 @@ registerModel({
readonly: true,
required: true,
}),
callView: one('CallView', {
related: 'callActionListView.callView',
required: true,
}),
},
});
+21 -165
View File
@@ -7,8 +7,6 @@ import { attr, many, one } from '@mail/model/model_field';
import { OnChange } from '@mail/model/model_onchange';
import { clear, insert, insertAndReplace, replace } from '@mail/model/model_field_command';
import { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'CallView',
identifyingFields: ['threadView'],
@@ -21,114 +19,12 @@ registerModel({
},
},
recordMethods: {
/**
* Finds a tile layout and dimensions that respects param0.aspectRatio while maximizing
* the total area covered by the tiles within the specified container dimensions.
*
* @param {Object} param0
* @param {number} [param0.aspectRatio]
* @param {number} param0.containerHeight
* @param {number} param0.containerWidth
* @param {number} param0.tileCount
*/
calculateTessellation({ aspectRatio = 1, containerHeight, containerWidth, tileCount }) {
let optimalLayout = {
area: 0,
cols: 0,
tileHeight: 0,
tileWidth: 0,
};
for (let columnCount = 1; columnCount <= tileCount; columnCount++) {
const rowCount = Math.ceil(tileCount / columnCount);
const potentialHeight = containerWidth / (columnCount * aspectRatio);
const potentialWidth = containerHeight / rowCount;
let tileHeight;
let tileWidth;
if (potentialHeight > potentialWidth) {
tileHeight = Math.floor(potentialWidth);
tileWidth = Math.floor(tileHeight * aspectRatio);
} else {
tileWidth = Math.floor(containerWidth / columnCount);
tileHeight = Math.floor(tileWidth / aspectRatio);
}
const area = tileHeight * tileWidth;
if (area <= optimalLayout.area) {
continue;
}
optimalLayout = {
area,
tileHeight,
tileWidth,
};
}
return optimalLayout;
},
/**
* @param {MouseEvent} ev
*/
onClick(ev) {
this._showOverlay();
},
/**
* @param {MouseEvent} ev
*/
onClickHideSidebar(ev) {
this.update({ hasSidebar: false });
},
/**
* @param {MouseEvent} ev
*/
onClickShowSidebar(ev) {
this.update({ hasSidebar: true });
},
/**
* @param {MouseEvent} ev
*/
onMouseleave(ev) {
if (ev.relatedTarget && ev.relatedTarget.closest('.o_CallActionList_popover')) {
// the overlay should not be hidden when the cursor leaves to enter the controller popover
return;
}
if (!this.exists()) {
return;
}
this.update({ showOverlay: false });
},
/**
* @param {MouseEvent} ev
*/
onMouseMove(ev) {
if (!this.exists()) {
return;
}
if (isEventHandled(ev, 'CallView.MouseMoveOverlay')) {
return;
}
this._showOverlay();
},
/**
* @param {MouseEvent} ev
*/
onMouseMoveOverlay(ev) {
if (!this.exists()) {
return;
}
markEventHandled(ev, 'CallView.MouseMoveOverlay');
this.update({
showOverlay: true,
showOverlayTimer: clear(),
});
},
/**
* @param {MouseEvent} ev
*/
onRtcSettingsDialogClosed(ev) {
this.messaging.userSetting.callSettingsMenu.toggle();
},
onShowOverlayTimeout() {
this.update({ showOverlay: false });
},
async activateFullScreen() {
const el = document.body;
try {
@@ -189,12 +85,6 @@ registerModel({
}
return clear();
},
/**
* @private
*/
_computeIsControllerFloating() {
return Boolean(this.isFullScreen || this.activeRtcSession && !this.threadView.compact);
},
/**
* @private
*/
@@ -208,7 +98,7 @@ registerModel({
if (this.mainParticipantCard) {
return false;
}
return !this.threadView.thread.rtc || this.threadView.thread.videoCount === 0;
return !this.channel.rtc || this.channel.videoCount === 0;
},
/**
* @private
@@ -226,7 +116,7 @@ registerModel({
}
return insert({
rtcSession: replace(this.activeRtcSession),
channel: replace(this.threadView.thread),
channel: replace(this.channel),
});
},
/**
@@ -249,7 +139,7 @@ registerModel({
const tileCards = [];
const sessionPartners = new Set();
const sessionGuests = new Set();
for (const rtcSession of this.threadView.thread.rtcSessions) {
for (const rtcSession of this.channel.rtcSessions) {
if (this.filterVideoGrid && !rtcSession.videoStream) {
continue;
}
@@ -257,25 +147,25 @@ registerModel({
rtcSession.guest && sessionPartners.add(rtcSession.guest.id);
tileCards.push({
rtcSession: replace(rtcSession),
channel: replace(this.threadView.thread),
channel: replace(this.channel),
});
}
for (const partner of this.threadView.thread.invitedPartners) {
for (const partner of this.channel.invitedPartners) {
if (sessionPartners.has(partner.id)) {
continue;
}
tileCards.push({
invitedPartner: replace(partner),
channel: replace(this.threadView.thread),
channel: replace(this.channel),
});
}
for (const guest of this.threadView.thread.invitedGuests) {
for (const guest of this.channel.invitedGuests) {
if (sessionGuests.has(guest.id)) {
continue;
}
tileCards.push({
invitedGuest: replace(guest),
channel: replace(this.threadView.thread),
channel: replace(this.channel),
});
}
return insertAndReplace(tileCards);
@@ -291,21 +181,10 @@ registerModel({
* @private
*/
_onChangeVideoCount() {
if (this.threadView.thread.videoCount === 0) {
if (this.channel.videoCount === 0) {
this.update({ filterVideoGrid: false });
}
},
/**
* Shows the overlay (buttons) for a set a mount of time.
*
* @private
*/
_showOverlay() {
this.update({
showOverlay: true,
showOverlayTimer: [clear(), insertAndReplace()],
});
},
/**
* @private
*/
@@ -330,13 +209,22 @@ registerModel({
default: 16 / 9,
compute: '_computeAspectRatio',
}),
callMainView: one('CallMainView', {
default: insertAndReplace(),
inverse: 'callView',
isCausal: true,
readonly: true,
}),
callSidebarView: one('CallSidebarView', {
compute: '_computeCallSideBarView',
inverse: 'callView',
isCausal: true,
readonly: true,
}),
tileContainerRef: attr(),
channel: one('Thread', {
related: 'threadView.thread',
required: true,
}),
/**
* Determines whether we only display the videos or all the participants
*/
@@ -349,13 +237,6 @@ registerModel({
hasSidebar: attr({
default: true,
}),
/**
* Determines if the controller is an overlay or a bottom bar.
*/
isControllerFloating: attr({
default: false,
compute: '_computeIsControllerFloating',
}),
/**
* Determines if the viewer should be displayed fullScreen.
*/
@@ -392,31 +273,12 @@ registerModel({
inverse: 'callView',
isCausal: true,
}),
/**
* The model for the controller (buttons).
*/
callActionListView: one('CallActionListView', {
default: insertAndReplace(),
readonly: true,
inverse: 'callView',
isCausal: true,
}),
/**
* Text content that is displayed on title of the settings dialog.
*/
settingsTitle: attr({
compute: '_computeSettingsTitle',
}),
/**
* Determines if we show the overlay with the control buttons.
*/
showOverlay: attr({
default: true,
}),
showOverlayTimer: one('Timer', {
inverse: 'callViewAsShowOverlay',
isCausal: true,
}),
/**
* ThreadView on which the call view is attached.
*/
@@ -425,9 +287,6 @@ registerModel({
readonly: true,
required: true,
}),
tileHeight: attr({
default: 0,
}),
/**
* List of all participant cards (can either be invitations or rtcSessions).
*/
@@ -436,17 +295,14 @@ registerModel({
inverse: 'callViewAsTile',
isCausal: true,
}),
tileWidth: attr({
default: 0,
}),
},
onChanges: [
new OnChange({
dependencies: ['threadView.thread.rtc'],
dependencies: ['channel.rtc'],
methodName: '_onChangeRtcChannel',
}),
new OnChange({
dependencies: ['threadView.thread.videoCount'],
dependencies: ['channel.videoCount'],
methodName: '_onChangeVideoCount',
}),
],
+5 -5
View File
@@ -7,7 +7,7 @@ import { clear } from '@mail/model/model_field_command';
registerModel({
name: 'Timer',
identifyingFields: [[
'callViewAsShowOverlay',
'callMainViewAsShowOverlay',
'chatterOwnerAsAttachmentsLoader',
'messagingOwnerAsFetchImStatusTimer',
'messageViewOwnerAsHighlight',
@@ -28,7 +28,7 @@ registerModel({
* @returns {integer|FieldCommand}
*/
_computeDuration() {
if (this.callViewAsShowOverlay) {
if (this.callMainViewAsShowOverlay) {
return 3 * 1000;
}
if (this.chatterOwnerAsAttachmentsLoader) {
@@ -83,8 +83,8 @@ registerModel({
* @private
*/
_onTimeoutOwner() {
if (this.callViewAsShowOverlay) {
this.callViewAsShowOverlay.onShowOverlayTimeout();
if (this.callMainViewAsShowOverlay) {
this.callMainViewAsShowOverlay.onShowOverlayTimeout();
return;
}
if (this.chatterOwnerAsAttachmentsLoader) {
@@ -122,7 +122,7 @@ registerModel({
},
},
fields: {
callViewAsShowOverlay: one('CallView', {
callMainViewAsShowOverlay: one('CallMainView', {
inverse: 'showOverlayTimer',
readonly: true,
}),