[IMP] mail, im_livechat: use ui service instead of store.isSmall

Since [1], the ui service is reactive which removes the needs for
the `mail.store` service to maintain a reactive version of `isSmall`.

This PR replaces the use of `store.isSmall` as well as `env.isSmall`
by the ui service.

1: https://github.com/odoo/odoo/pull/121343

closes odoo/odoo#121590

Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
This commit is contained in:
tsm-odoo
2023-05-22 11:42:58 +02:00
parent fc002590db
commit ea3008cc49
35 changed files with 85 additions and 65 deletions
@@ -27,7 +27,7 @@ patch(ThreadService.prototype, "im_livechat", {
*/
setDiscussThread(thread, pushState) {
this._super(thread, pushState);
if (this.store.isSmall && thread.type === "livechat") {
if (this.ui.isSmall && thread.type === "livechat") {
this.store.discuss.activeTab = "livechat";
}
},
@@ -32,7 +32,7 @@ Model({
globalWindowInnerHeight: this.messaging.browser.innerHeight,
globalWindowInnerWidth: this.messaging.browser.innerWidth,
isMobileDevice: isMobileOS(),
isSmall: this.env.isSmall,
isSmall: this.env.services.ui.isSmall,
sizeClass: this.env.services.ui.size,
});
},
@@ -1,6 +1,7 @@
/* @odoo-module */
import { Component, useExternalListener, useRef, useState } from "@odoo/owl";
import { useService } from "@web/core/utils/hooks";
/**
* @typedef {Object} Props
@@ -41,6 +42,7 @@ export class AttachmentViewer extends Component {
scale: 1,
angle: 0,
});
this.ui = useState(useService("ui"));
}
onImageLoaded() {
@@ -33,10 +33,10 @@
</div>
<img t-on-click.stop="" t-on-load="onImageLoaded" t-on-wheel="onWheelImage" t-on-mousedown.stop="onMousedownImage" t-on-mouseup.stop="onMouseupImage" class="o-mail-AttachmentViewer-view o-mail-AttachmentViewer-viewImage mw-100 mh-100 transition-base" t-att-src="state.attachment.defaultSource" t-att-style="imageStyle" draggable="false" alt="Viewer" t-ref="image"/>
</div>
<iframe t-if="state.attachment.isPdf" class="o-mail-AttachmentViewer-view w-75 h-100 border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': env.isSmall }" t-att-src="state.attachment.defaultSource"/>
<iframe t-if="state.attachment.isPdf" class="o-mail-AttachmentViewer-view w-75 h-100 border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': ui.isSmall }" t-att-src="state.attachment.defaultSource"/>
<iframe t-if="state.attachment.isText" class="o-mail-AttachmentViewer-view o-isText o_text w-75 h-100 border-0" t-att-src="state.attachment.defaultSource"/>
<iframe t-if="state.attachment.isUrlYoutube" allow="autoplay; encrypted-media" class="o-mail-AttachmentViewer-view w-75 h-100 border-0" t-att-src="state.attachment.defaultSource" height="315" width="560"/>
<video t-if="state.attachment.isVideo" class="o-mail-AttachmentViewer-view w-75 h-75" t-att-class="{ 'w-100 h-100': env.isSmall }" t-on-click.stop="" controls="controls">
<video t-if="state.attachment.isVideo" class="o-mail-AttachmentViewer-view w-75 h-75" t-att-class="{ 'w-100 h-100': ui.isSmall }" t-on-click.stop="" controls="controls">
<source t-att-data-type="state.attachment.mimetype" t-att-src="state.attachment.defaultSource"/>
</video>
</div>
@@ -73,6 +73,7 @@ export class Composer extends Component {
this.messageService = useState(useService("mail.message"));
/** @type {import("@mail/core/thread_service").ThreadService} */
this.threadService = useService("mail.thread");
this.ui = useState(useService("ui"));
this.ref = useRef("textarea");
this.fakeTextarea = useRef("fakeTextarea");
this.state = useState({
+4 -4
View File
@@ -62,8 +62,8 @@
</div>
<div class="o-mail-Composer-actions d-flex bg-view"
t-att-class="{
'ms-1' : compact and store.isSmall,
'mx-1 me-2' : compact and !store.isSmall,
'ms-1' : compact and ui.isSmall,
'mx-1 me-2' : compact and !ui.isSmall,
'ms-3' : normal,
'mx-3 border-top p-1' : extended,
}"
@@ -96,14 +96,14 @@
</t>
<t t-name="mail.Composer.sendButton" owl="1">
<button t-if="!compact or store.isSmall" class="o-mail-Composer-send btn btn-link o-last border-start-0"
<button t-if="!compact or ui.isSmall" class="o-mail-Composer-send btn btn-link o-last border-start-0"
t-att-class="{'mt-2': extended, 'rounded-0 rounded-end-3': !extended and !compact, 'align-self-stretch': !extended, 'border-start-0': env.inDiscussApp}"
t-on-click="sendMessage"
type="button"
t-att-disabled="isSendButtonDisabled"
aria-label="Send"
>
<t t-if="store.isSmall"><i class="fa fa-paper-plane-o"/></t>
<t t-if="ui.isSmall"><i class="fa fa-paper-plane-o"/></t>
<t t-else="">Send</t>
</button>
</t>
+2 -4
View File
@@ -14,7 +14,7 @@ export class Store {
setup(env) {
this.env = env;
this.isSmall = env.isSmall;
this.discuss.activeTab = this.env.services.ui.isSmall ? "mailbox" : "all";
}
async updateBusSubscription() {
@@ -149,10 +149,8 @@ export const storeService = {
start(env, services) {
const res = reactive(new Store(env, services));
onChange(res, "threads", () => res.updateBusSubscription());
res.discuss.activeTab = res.isSmall ? "mailbox" : "all";
services.ui.bus.addEventListener("resize", () => {
res.isSmall = services.ui.isSmall;
if (!res.isSmall) {
if (!services.ui.isSmall) {
res.discuss.activeTab = "all";
} else {
res.discuss.activeTab = res.threads[res.discuss.threadLocalId]?.type ?? "all";
@@ -39,6 +39,7 @@ export class ThreadService {
this.rpc = services.rpc;
this.notificationService = services.notification;
this.router = services.router;
this.ui = services.ui;
/** @type {import("@mail/core/persona_service").PersonaService} */
this.personaService = services["mail.persona"];
/** @type {import("@mail/core/message_service").MessageService} */
@@ -637,7 +638,7 @@ export class ThreadService {
typeof thread.id === "string"
? `mail.box_${thread.id}`
: `discuss.channel_${thread.id}`;
this.store.discuss.activeTab = !this.store.isSmall
this.store.discuss.activeTab = !this.ui.isSmall
? "all"
: thread.model === "mail.box"
? "mailbox"
@@ -1101,6 +1102,7 @@ export const threadService = {
"router",
"mail.persona",
"mail.message",
"ui",
],
start(env, services) {
return new ThreadService(env, services);
@@ -24,6 +24,7 @@ export class MessageReactionMenu extends Component {
this.threadService = useService("mail.thread");
this.root = useRef("root");
this.store = useStore();
this.ui = useState(useService("ui"));
this.messageService = useService("mail.message");
this.state = useState({
reaction: this.props.message.reactions[0],
@@ -13,7 +13,7 @@
</t>
</div>
<div class="d-flex overflow-auto flex-column flex-grow-1 bg-view p-2 h-100">
<div t-foreach="state.reaction.personas" t-as="persona" t-key="persona.id" class="o-mail-MessageReactionMenu-persona d-flex p-1 align-items-center" t-att-class="{ 'o-isDeviceSmall': store.isSmall }">
<div t-foreach="state.reaction.personas" t-as="persona" t-key="persona.id" class="o-mail-MessageReactionMenu-persona d-flex p-1 align-items-center" t-att-class="{ 'o-isDeviceSmall': ui.isSmall }">
<img class="rounded-circle o_object_fit_cover o-mail-MessageReactionMenu-avatar" t-att-src="threadService.avatarUrl(persona, props.message.originThread)"/>
<span class="d-flex flex-grow-1 align-items-center">
<span class="mx-2 text-truncate fs-6" t-esc="persona.name"/>
@@ -14,6 +14,7 @@ export class MessageReactions extends Component {
this.user = useService("user");
this.messaging = useService("mail.messaging");
this.store = useStore();
this.ui = useService("ui");
this.messageService = useState(useService("mail.message"));
}
@@ -72,7 +73,7 @@ export class MessageReactions extends Component {
}
onContextMenu(ev) {
if (this.store.isSmall) {
if (this.ui.isSmall) {
ev.preventDefault();
this.props.openReactionMenu();
}
+1
View File
@@ -52,6 +52,7 @@ export class Thread extends Component {
this.state = useState({ isReplyingTo: false, showJumpPresent: false });
/** @type {import("@mail/core/thread_service").ThreadService} */
this.threadService = useState(useService("mail.thread"));
this.ui = useState(useService("ui"));
if (!this.env.inChatter || !this.props.hasScrollAdjust) {
useAutoScroll("messages", () => {
if (this.env.messageHighlight?.highlightedMessageId) {
+1 -1
View File
@@ -115,7 +115,7 @@
<t t-name="mail.NotificationMessage" owl="1">
<div class="mx-auto text-500 small px-3 text-center" t-on-click="onClickNotification" t-att-class="{
'o-mail-Thread-noPinnedPanel': env.inChatWindow and !env.isSmall,
'o-mail-Thread-noPinnedPanel': env.inChatWindow and !ui.isSmall,
'mt-2': prevMsg and !prevMsg.isNotification,
}">
<i t-if="msg.notificationIcon" t-attf-class="{{ msg.notificationIcon }} me-1"/>
@@ -19,6 +19,7 @@ export class ChannelInvitation extends Component {
this.notification = useService("notification");
this.threadService = useState(useService("mail.thread"));
this.personaService = useService("mail.persona");
this.ui = useService("ui");
this.inputRef = useRef("input");
this.searchStr = "";
this.state = useState({
@@ -107,7 +108,7 @@ export class ChannelInvitation extends Component {
}
);
}
if (this.env.isSmall) {
if (this.ui.isSmall) {
this.props.chatState.activeMode = "";
} else {
this.props.close();
@@ -2,7 +2,7 @@
<templates xml:space="preserve">
<t t-inherit="mail.ChatWindow" t-inherit-mode="extension">
<xpath expr="//*[@t-ref='startCall']" position="after">
<div t-if="env.isSmall and thread and thread.type === 'channel'" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" t-att-title="state.activeMode === 'add-users' ? 'Stop adding users' : 'Add users'" t-on-click.stop="toggleAddUsers">
<div t-if="ui.isSmall and thread and thread.type === 'channel'" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" t-att-title="state.activeMode === 'add-users' ? 'Stop adding users' : 'Add users'" t-on-click.stop="toggleAddUsers">
<i class="fa fa-lg fa-user-plus"/>
</div>
</xpath>
@@ -62,6 +62,7 @@ export class Discuss extends Component {
this.state = useState({ activeMode: this.MODES.NONE });
this.orm = useService("orm");
this.effect = useService("effect");
this.ui = useState(useService("ui"));
this.prevInboxCounter = this.store.discuss.inbox.counter;
useChildSubEnv({
inDiscussApp: true,
@@ -2,9 +2,9 @@
<templates xml:space="preserve">
<t t-name="mail.Discuss" owl="1">
<div class="o-mail-Discuss d-flex h-100 flex-grow-1 border-top" t-att-class="{ 'flex-column align-items-center bg-view': store.isSmall }" t-ref="root">
<div t-if="store.isSmall and store.discuss.activeTab === 'mailbox'" class="w-100 border-bottom" t-call="mail.Discuss.mobileTopbar" t-ref="mobileTopbar"/>
<div t-if="thread and !(store.isSmall and ['chat', 'channel'].includes(store.discuss.activeTab))" class="o-mail-Discuss-content d-flex flex-column h-100 w-100 bg-view overflow-auto" t-ref="content">
<div class="o-mail-Discuss d-flex h-100 flex-grow-1 border-top" t-att-class="{ 'flex-column align-items-center bg-view': ui.isSmall }" t-ref="root">
<div t-if="ui.isSmall and store.discuss.activeTab === 'mailbox'" class="w-100 border-bottom" t-call="mail.Discuss.mobileTopbar" t-ref="mobileTopbar"/>
<div t-if="thread and !(ui.isSmall and ['chat', 'channel'].includes(store.discuss.activeTab))" class="o-mail-Discuss-content d-flex flex-column h-100 w-100 bg-view overflow-auto" t-ref="content">
<div class="o-mail-Discuss-header px-3 bg-view border-bottom d-flex flex-shrink-0 align-items-center" style="height:46px;">
<div t-if="thread and thread.model === 'discuss.channel'" class="o-mail-Discuss-threadAvatar position-relative align-self-center align-items-center mt-2 mb-2 me-2">
<img class="rounded" t-att-src="channelAvatar" alt="Thread Image"/>
@@ -48,7 +48,7 @@
<button t-if="thread.allowCalls and thread.type !== 'mailbox' and thread !== rtc.state.channel" class="btn rounded px-2 shadow-none" title="Start a Call" t-on-click="() => this.rtc.toggleCall(this.thread)" t-ref="startCall">
<i class="fa fa-lg fa-phone text-700"/>
</button>
<div t-if="props.public and !store.isSmall" class="d-flex align-items-center">
<div t-if="props.public and !ui.isSmall" class="d-flex align-items-center">
<img class="o-mail-Discuss-selfAvatar mx-1 rounded-circle o_object_fit_cover flex-shrink-0" alt="Avatar" t-att-src="threadService.avatarUrl(store.self, thread)"/>
<div class="lead fw-bold flex-shrink-1 text-dark">
<t t-if="store.user" t-esc="store.user.name"/>
@@ -72,7 +72,7 @@
<PinnedMessagesPanel t-if="state.activeMode === MODES.PINNED_MESSAGES and thread.model === 'discuss.channel'" thread="thread"/>
</div>
</div>
<div t-if="!thread and (!store.isSmall or store.discuss.activeTab === 'mailbox')" class="d-flex flex-grow-1 align-items-center justify-content-center w-100 bg-view">
<div t-if="!thread and (!ui.isSmall or store.discuss.activeTab === 'mailbox')" class="d-flex flex-grow-1 align-items-center justify-content-center w-100 bg-view">
<h4 class="text-muted"><b><i>No conversation selected.</i></b></h4>
</div>
</div>
@@ -1,6 +1,6 @@
/* @odoo-module */
import { Component, onWillStart, onWillUpdateProps, useSubEnv } from "@odoo/owl";
import { Component, onWillStart, onWillUpdateProps, useState, useSubEnv } from "@odoo/owl";
import { Message } from "@mail/core_ui/message";
import { MessageConfirmDialog } from "@mail/core_ui/message_confirm_dialog";
import { useService } from "@web/core/utils/hooks";
@@ -18,6 +18,8 @@ export class PinnedMessagesPanel extends Component {
this.messageService = useService("mail.message");
this.store = useService("mail.store");
this.rpc = useService("rpc");
this.ui = useState(useService("ui"));
onWillStart(() => {
this.threadService.fetchPinnedMessages(this.props.thread);
});
@@ -38,7 +40,7 @@ export class PinnedMessagesPanel extends Component {
* @param {Message} message
*/
async onClickJump(message) {
if (this.env.isSmall) {
if (this.ui.isSmall) {
this.env.pinMenu.close();
// Give the time to the pin menu to close before scrolling
// to the message.
@@ -11,8 +11,8 @@
<div class="card m-3 mt-0" t-foreach="props.thread.pinnedMessages" t-as="message" t-key="message.id">
<div class="card-body">
<div class="d-flex justify-content-end">
<button class="o-mail-PinnedMessagesPanel-jump btn rounded bg-400 badge opacity-0 flex-shrink-0" t-att-class="{ 'opacity-100 py-1 px-2': env.isSmall }" t-on-click="() => this.onClickJump(message)">Jump</button>
<button class="btn ms-2 p-0 text-700 shadow-none" t-att-class="{ 'fs-5 align-self-end': env.isSmall }" title="Unpin"
<button class="o-mail-PinnedMessagesPanel-jump btn rounded bg-400 badge opacity-0 flex-shrink-0" t-att-class="{ 'opacity-100 py-1 px-2': ui.isSmall }" t-on-click="() => this.onClickJump(message)">Jump</button>
<button class="btn ms-2 p-0 text-700 shadow-none" t-att-class="{ 'fs-5 align-self-end': ui.isSmall }" title="Unpin"
t-on-click="() => this.onClickUnpin(message)">
<i class="d-block fa fa-times"/>
</button>
@@ -2,7 +2,7 @@
import { useMessaging } from "@mail/core/messaging_hook";
import { Component } from "@odoo/owl";
import { Component, useState } from "@odoo/owl";
import { Layout } from "@web/search/layout";
import { standardViewProps } from "@web/views/standard_view_props";
@@ -41,6 +41,7 @@ export class ActivityController extends Component {
this.action = useService("action");
this.messaging = useMessaging();
this.activity = useService("mail.activity");
this.ui = useState(useService("ui"));
}
scheduleActivity() {
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityController" owl="1">
<div t-att-class="props.className + (env.isSmall ? ' o_action_delegate_scroll': '')">
<div t-att-class="props.className + (ui.isSmall ? ' o_action_delegate_scroll': '')">
<Layout display="props.display">
<t t-set-slot="control-panel-additional-actions">
<CogMenu/>
@@ -2,7 +2,7 @@
import { useMessaging, useStore } from "@mail/core/messaging_hook";
import { Component } from "@odoo/owl";
import { Component, useState } from "@odoo/owl";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { registry } from "@web/core/registry";
@@ -18,6 +18,7 @@ export class ActivityMenu extends Component {
this.store = useStore();
this.action = useService("action");
this.userId = useService("user").userId;
this.ui = useState(useService("ui"));
this.fetchSystrayActivities();
}
@@ -2,13 +2,13 @@
<templates xml:space="preserve">
<t t-name="mail.ActivityMenu" owl="1">
<Dropdown position="'bottom-end'" autoOpen="false" beforeOpen.bind="onBeforeOpen" menuClass="`p-0 ${ store.isSmall ? 'start-0 w-100 mh-100 bottom-0 d-flex flex-column' : ''}`">
<Dropdown position="'bottom-end'" autoOpen="false" beforeOpen.bind="onBeforeOpen" menuClass="`p-0 ${ ui.isSmall ? 'start-0 w-100 mh-100 bottom-0 d-flex flex-column' : ''}`">
<t t-set-slot="toggler">
<i class="fa fa-lg fa-clock-o" role="img" aria-label="Activities"></i>
<span t-if="store.activityCounter" class="o-mail-ActivityMenu-counter badge rounded-pill"><t t-esc="store.activityCounter"/></span>
</t>
<t t-set-slot="default">
<div class="o-mail-ActivityMenu bg-view flex-grow-1 d-flex flex-column" t-att-class="{ 'overflow-auto w-100 mh-100': store.isSmall }">
<div class="o-mail-ActivityMenu bg-view flex-grow-1 d-flex flex-column" t-att-class="{ 'overflow-auto w-100 mh-100': ui.isSmall }">
<div t-if="store.activityGroups.length === 0" class="o-mail-ActivityMenu-empty align-items-center text-muted p-2 opacity-50 d-flex justify-content-center">
<span>Congratulations, you're done with your activities.</span>
</div>
@@ -57,6 +57,7 @@ export class ChatWindow extends Component {
activeMode: "",
});
this.action = useService("action");
this.ui = useState(useService("ui"));
this.contentRef = useRef("content");
useChildSubEnv({
inChatWindow: true,
@@ -77,10 +78,10 @@ export class ChatWindow extends Component {
}
get style() {
const maxHeight = !this.store.isSmall ? "max-height: 95vh;" : "";
const maxHeight = !this.ui.isSmall ? "max-height: 95vh;" : "";
const textDirection = localization.direction;
const offsetFrom = textDirection === "rtl" ? "left" : "right";
const visibleOffset = this.store.isSmall ? 0 : this.props.right;
const visibleOffset = this.ui.isSmall ? 0 : this.props.right;
const oppositeFrom = offsetFrom === "right" ? "left" : "right";
return `${offsetFrom}: ${visibleOffset}px; ${oppositeFrom}: auto; ${maxHeight}`;
}
@@ -112,7 +113,7 @@ export class ChatWindow extends Component {
}
toggleFold() {
if (this.store.isSmall) {
if (this.ui.isSmall) {
return;
}
if (this.props.chatWindow.hidden) {
@@ -5,19 +5,19 @@
<div class="o-mail-ChatWindow"
t-att-style="style"
t-att-class="{
'w-100 h-100': store.isSmall,
'w-100 h-100': ui.isSmall,
'o-folded': props.chatWindow.folded,
'position-absolute bottom-0 overflow-hidden d-flex flex-column': !props.chatWindow.hidden,
'rounded-top-3': !props.chatWindow.hidden and !store.isSmall,
'rounded-top-3': !props.chatWindow.hidden and !ui.isSmall,
}"
t-on-keydown.stop="onKeydown"
tabindex="1"
>
<div class="o-mail-ChatWindow-header d-flex align-items-center flex-shrink-0 bg-100" t-on-click="toggleFold" t-att-class="{ 'cursor-pointer': !store.isSmall, 'border-bottom': !props.chatWindow.folded }">
<div t-if="store.isSmall" class="cursor-pointer d-flex align-items-center h-100 px-3 py-0 me-2" title="Close conversation" t-on-click="() => this.close()">
<div class="o-mail-ChatWindow-header d-flex align-items-center flex-shrink-0 bg-100" t-on-click="toggleFold" t-att-class="{ 'cursor-pointer': !ui.isSmall, 'border-bottom': !props.chatWindow.folded }">
<div t-if="ui.isSmall" class="cursor-pointer d-flex align-items-center h-100 px-3 py-0 me-2" title="Close conversation" t-on-click="() => this.close()">
<i class="oi oi-arrow-left"/>
</div>
<div t-if="thread and thread.model === 'discuss.channel'" class="o-mail-ChatWindow-threadAvatar position-relative align-self-center align-items-center my-0" t-att-class="{ 'ms-3': !store.isSmall, 'me-1': thread.type === 'chat', 'me-2': thread.type !== 'chat' }">
<div t-if="thread and thread.model === 'discuss.channel'" class="o-mail-ChatWindow-threadAvatar position-relative align-self-center align-items-center my-0" t-att-class="{ 'ms-3': !ui.isSmall, 'me-1': thread.type === 'chat', 'me-2': thread.type !== 'chat' }">
<img class="rounded" t-att-src="thread.imgUrl" alt="Thread Image"/>
</div>
<t t-else="">
@@ -32,14 +32,14 @@
(<t t-out="thread.message_unread_counter"/>)
</div>
<div class="flex-grow-1"/>
<div t-if="thread?.model === 'discuss.channel' and env.isSmall" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Pinned messages" t-on-click.stop="togglePinMenu">
<div t-if="thread?.model === 'discuss.channel' and ui.isSmall" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Pinned messages" t-on-click.stop="togglePinMenu">
<i class="fa fa-lg fa-thumb-tack"/>
</div>
<div t-if="thread and thread.allowCalls and thread !== rtc.state.channel and !props.chatWindow.hidden" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Start a Call" t-on-click.stop="() => this.rtc.toggleCall(this.props.chatWindow.thread)" t-ref="startCall">
<i class="fa fa-phone"/>
</div>
<t t-if="thread">
<div t-if="!env.isSmall" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Open in Discuss" t-on-click.stop="expand" t-ref="expand">
<div t-if="!ui.isSmall" class="o-mail-ChatWindow-command d-flex align-items-center h-100 px-3 py-0 m-0 opacity-75 opacity-100-hover" title="Open in Discuss" t-on-click.stop="expand" t-ref="expand">
<i class="fa fa-expand"/>
</div>
</t>
@@ -45,6 +45,7 @@ export class ChatWindowContainer extends Component {
this.messaging = useMessaging();
this.store = useStore();
this.chatWindowService = useState(useService("mail.chat_window"));
this.ui = useState(useService("ui"));
this.hiddenMenuRef = useRef("hiddenMenu");
useEffect(
() => this.setHiddenMenuOffset(),
@@ -2,8 +2,8 @@
<templates xml:space="preserve">
<t t-name="mail.ChatWindowContainer" owl="1">
<div class="o-mail-ChatWindowContainer" t-if="!store.discuss.isActive or store.isSmall">
<div t-if="chatWindowService.hidden.length > 0 and !store.isSmall" t-ref="hiddenMenu" class="o-mail-ChatWindow-hiddenMenuContainer position-fixed bottom-0">
<div class="o-mail-ChatWindowContainer" t-if="!store.discuss.isActive or ui.isSmall">
<div t-if="chatWindowService.hidden.length > 0 and !ui.isSmall" t-ref="hiddenMenu" class="o-mail-ChatWindow-hiddenMenuContainer position-fixed bottom-0">
<t t-call="mail.ChatWindowHiddenMenu"/>
</div>
<t t-foreach="chatWindowService.visible" t-as="chatWindow" t-key="chatWindow.threadLocalId">
@@ -16,6 +16,7 @@ export class ChatWindowService {
/** @type {import("@mail/core/store_service").Store} */
this.store = services["mail.store"];
this.orm = services.orm;
this.ui = services.ui;
}
openNewMessage() {
@@ -42,12 +43,12 @@ export class ChatWindowService {
}
get maxVisible() {
const startGap = this.store.isSmall
const startGap = this.ui.isSmall
? 0
: this.hidden.length > 0
? CHAT_WINDOW_END_GAP_WIDTH + CHAT_WINDOW_HIDDEN_WIDTH
: CHAT_WINDOW_END_GAP_WIDTH;
const endGap = this.store.isSmall ? 0 : CHAT_WINDOW_END_GAP_WIDTH;
const endGap = this.ui.isSmall ? 0 : CHAT_WINDOW_END_GAP_WIDTH;
const available = browser.innerWidth - startGap - endGap;
const maxAmountWithoutHidden = Math.floor(
available / (CHAT_WINDOW_WIDTH + CHAT_WINDOW_INBETWEEN_WIDTH)
@@ -56,7 +57,7 @@ export class ChatWindowService {
}
notifyState(chatWindow) {
if (this.env.isSmall) {
if (this.ui.isSmall) {
return;
}
if (chatWindow.thread?.model === "discuss.channel") {
@@ -142,7 +143,7 @@ export class ChatWindowService {
}
async close(chatWindow, { escape = false } = {}) {
if (this.store.isSmall && !this.store.discuss.isActive) {
if (this.ui.isSmall && !this.store.discuss.isActive) {
// If we are in mobile and discuss is not open, it means the
// chat window was opened from the messaging menu. In that
// case it should be re-opened to simulate it was always
@@ -171,7 +172,7 @@ export class ChatWindowService {
}
export const chatWindowService = {
dependencies: ["mail.store", "orm"],
dependencies: ["mail.store", "orm", "ui"],
start(env, services) {
return new ChatWindowService(env, services);
},
@@ -2,10 +2,10 @@
<templates xml:space="preserve">
<t t-inherit="mail.Discuss" t-inherit-mode="extension">
<xpath expr="//*[@t-ref='mobileTopbar']" position="before">
<Sidebar t-if="!props.public and !store.isSmall"/>
<Sidebar t-if="!props.public and !ui.isSmall"/>
</xpath>
<xpath expr="//*[@t-ref='root']" position="inside">
<MessagingMenu t-if="store.isSmall"/>
<MessagingMenu t-if="ui.isSmall"/>
</xpath>
</t>
</templates>
@@ -31,6 +31,7 @@ export class MessagingMenu extends Component {
/** @type {import("@mail/core/thread_service").ThreadService} */
this.threadService = useState(useService("mail.thread"));
this.action = useService("action");
this.ui = useState(useService("ui"));
this.state = useState({
addingChat: false,
addingChannel: false,
@@ -201,7 +202,7 @@ export class MessagingMenu extends Component {
}
onClickNewMessage() {
if (this.store.isSmall && this.env.inDiscussApp) {
if (this.ui.isSmall && this.env.inDiscussApp) {
this.state.addingChat = true;
} else {
this.chatWindowService.openNewMessage();
@@ -2,9 +2,9 @@
<templates xml:space="preserve">
<t t-name="mail.MessagingMenu" owl="1">
<Dropdown t-if="!env.inDiscussApp" autoOpen="false" beforeOpen.bind="beforeOpen" onStateChanged="state => this.state.isOpen = state.open" position="'bottom-end'" menuClass="`p-0 ${ store.isSmall ? 'start-0 w-100 mh-100 bottom-0 d-flex flex-column' : ''}`">
<Dropdown t-if="!env.inDiscussApp" autoOpen="false" beforeOpen.bind="beforeOpen" onStateChanged="state => this.state.isOpen = state.open" position="'bottom-end'" menuClass="`p-0 ${ ui.isSmall ? 'start-0 w-100 mh-100 bottom-0 d-flex flex-column' : ''}`">
<t t-set-slot="toggler">
<i class="fa fa-lg fa-comments" role="img" aria-label="Messages" t-on-click="() => store.discuss.activeTab = store.isSmall and store.discuss.activeTab === 'mailbox' ? 'all' : store.discuss.activeTab"></i>
<i class="fa fa-lg fa-comments" role="img" aria-label="Messages" t-on-click="() => store.discuss.activeTab = ui.isSmall and store.discuss.activeTab === 'mailbox' ? 'all' : store.discuss.activeTab"></i>
<span t-if="counter" class="o-mail-MessagingMenu-counter badge rounded-pill"><t t-esc="counter"/></span>
</t>
<t t-set-slot="default">
@@ -16,17 +16,17 @@
<t t-name="mail.MessagingMenu.content" owl="1">
<t t-set="failures" t-value="store.notificationGroups"/>
<div class="o-mail-MessagingMenu d-flex flex-column flex-grow-1" t-att-class="{ 'overflow-auto w-100 mh-100': store.isSmall }">
<div t-att-class="{'flex-row-reverse text-uppercase': store.isSmall,
<div class="o-mail-MessagingMenu d-flex flex-column flex-grow-1" t-att-class="{ 'overflow-auto w-100 mh-100': ui.isSmall }">
<div t-att-class="{'flex-row-reverse text-uppercase': ui.isSmall,
'o-mail-MessagingMenu-header border-bottom d-flex flex-shrink-0 p-2': !env.inDiscussApp,
}">
<t t-if="!store.isSmall">
<t t-if="!ui.isSmall">
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'all' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'all'">All</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'chat' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'chat'">Chats</button>
<button class="btn btn-link" t-att-class="store.discuss.activeTab === 'channel' ? 'fw-bolder' : 'text-500'" type="button" role="tab" t-on-click="() => store.discuss.activeTab = 'channel'">Channels</button>
</t>
<div class="flex-grow-1"></div>
<t t-if="!store.discuss.isActive or store.isSmall and store.discuss.activeTab !== 'mailbox'">
<t t-if="!store.discuss.isActive or ui.isSmall and store.discuss.activeTab !== 'mailbox'">
<t t-if="env.inDiscussApp and ['chat', 'channel'].includes(store.discuss.activeTab)">
<div t-if="state.addingChat or state.addingChannel" class="p-4 border-bottom bg-light" t-ref="selector">
<t t-if="state.addingChat" t-set="category" t-value="store.discuss.chats"/>
@@ -103,7 +103,7 @@
</t>
</div>
</div>
<div t-if="store.isSmall" class="o-mail-MessagingMenu-navbar d-flex border-top bg-view shadow-lg w-100">
<div t-if="ui.isSmall" class="o-mail-MessagingMenu-navbar d-flex border-top bg-view shadow-lg w-100">
<button t-foreach="tabs" t-key="tab.id" t-as="tab" class="o-mail-MessagingMenu-tab btn d-flex flex-column align-items-center flex-grow-1 flex-basis-0 p-2" t-att-class="{
'text-primary fw-bolder': store.discuss.activeTab === tab.id,
'border-end': !tab_last,
@@ -9,6 +9,7 @@ patch(Messaging.prototype, "mail/web", {
this._super(env, services, initialThreadLocalId);
/** @type {import("@mail/chat/chat_window_service").ChatWindow} */
this.chatWindowService = services["mail.chat_window"];
this.ui = services["ui"];
},
initMessagingCallback(data) {
this.loadFailures();
@@ -29,7 +30,7 @@ patch(Messaging.prototype, "mail/web", {
const channel = this.store.threads[createLocalId("discuss.channel", notif.payload.id)];
const message = this.store.messages[notif.payload.message.id];
if (
!this.store.isSmall &&
!this.ui.isSmall &&
channel.correspondent !== this.store.odoobot &&
!message.isSelfAuthored
) {
@@ -39,5 +40,5 @@ patch(Messaging.prototype, "mail/web", {
});
patch(messagingService, "mail/web", {
dependencies: [...messagingService.dependencies, "mail.chat_window"],
dependencies: [...messagingService.dependencies, "mail.chat_window", "ui"],
});
@@ -170,7 +170,7 @@ patch(ThreadService.prototype, "mail/web", {
thread.suggestedRecipients = recipients;
},
open(thread, replaceNewMessageChatWindow) {
if (!this.store.discuss.isActive || this.store.isSmall) {
if (!this.store.discuss.isActive || this.ui.isSmall) {
const chatWindow = this.chatWindowService.insert({
folded: false,
thread,
@@ -763,7 +763,7 @@ QUnit.test(
900,
"should not have enough space to open 3 chat windows simultaneously"
);
const { env, openDiscuss } = await start();
const { openDiscuss } = await start();
await openDiscuss();
// open, from systray menu, chat windows of channels with id 1, 2, 3
await click(".o_menu_systray i[aria-label='Messages']");
@@ -773,7 +773,10 @@ QUnit.test(
await click(".o_menu_systray i[aria-label='Messages']");
await click(".o-mail-NotificationItem-name:contains(Channel-3)");
// simulate resize to go into mobile
await afterNextRender(() => (env.services["mail.store"].isSmall = true));
await afterNextRender(() => {
patchUiSize({ size: SIZES.SM });
window.dispatchEvent(new UIEvent("resize"));
});
assert.containsNone($, ".o-mail-ChatWindowHiddenToggler");
}
);
@@ -418,7 +418,7 @@ async function start(param0 = {}) {
await afterNextRender(async () => {
webClient = await getWebClientReady({ ...param0, messagingBus });
});
if (webClient.env.isSmall) {
if (webClient.env.services.ui.isSmall) {
target.style.width = "100%";
}
const openView = async (action, options) => {