[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:
@@ -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,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user