summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/models/chat_window
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/models/chat_window
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mail/static/src/models/chat_window')
-rw-r--r--addons/mail/static/src/models/chat_window/chat_window.js480
1 files changed, 480 insertions, 0 deletions
diff --git a/addons/mail/static/src/models/chat_window/chat_window.js b/addons/mail/static/src/models/chat_window/chat_window.js
new file mode 100644
index 00000000..49e22742
--- /dev/null
+++ b/addons/mail/static/src/models/chat_window/chat_window.js
@@ -0,0 +1,480 @@
+odoo.define('mail/static/src/models/chat_window/chat_window.js', function (require) {
+'use strict';
+
+const { registerNewModel } = require('mail/static/src/model/model_core.js');
+const { attr, many2one, one2many, one2one } = require('mail/static/src/model/model_field.js');
+const { clear } = require('mail/static/src/model/model_field_command.js');
+
+function factory(dependencies) {
+
+ class ChatWindow extends dependencies['mail.model'] {
+
+ /**
+ * @override
+ */
+ _created() {
+ const res = super._created(...arguments);
+ this._onShowHomeMenu.bind(this);
+ this._onHideHomeMenu.bind(this);
+
+ this.env.messagingBus.on('hide_home_menu', this, this._onHideHomeMenu);
+ this.env.messagingBus.on('show_home_menu', this, this._onShowHomeMenu);
+ return res;
+ }
+
+ /**
+ * @override
+ */
+ _willDelete() {
+ this.env.messagingBus.off('hide_home_menu', this, this._onHideHomeMenu);
+ this.env.messagingBus.off('show_home_menu', this, this._onShowHomeMenu);
+ return super._willDelete(...arguments);
+ }
+
+ //----------------------------------------------------------------------
+ // Public
+ //----------------------------------------------------------------------
+
+ /**
+ * Close this chat window.
+ *
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.notifyServer]
+ */
+ close({ notifyServer } = {}) {
+ if (notifyServer === undefined) {
+ notifyServer = !this.env.messaging.device.isMobile;
+ }
+ const thread = this.thread;
+ this.delete();
+ // Flux specific: 'closed' fold state should only be saved on the
+ // server when manually closing the chat window. Delete at destroy
+ // or sync from server value for example should not save the value.
+ if (thread && notifyServer) {
+ thread.notifyFoldStateToServer('closed');
+ }
+ if (this.env.device.isMobile && !this.env.messaging.discuss.isOpen) {
+ // 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
+ // there in the background.
+ this.env.messaging.messagingMenu.update({ isOpen: true });
+ }
+ }
+
+ expand() {
+ if (this.thread) {
+ this.thread.open({ expanded: true });
+ }
+ }
+
+ /**
+ * Programmatically auto-focus an existing chat window.
+ */
+ focus() {
+ this.update({ isDoFocus: true });
+ }
+
+ focusNextVisibleUnfoldedChatWindow() {
+ const nextVisibleUnfoldedChatWindow = this._getNextVisibleUnfoldedChatWindow();
+ if (nextVisibleUnfoldedChatWindow) {
+ nextVisibleUnfoldedChatWindow.focus();
+ }
+ }
+
+ focusPreviousVisibleUnfoldedChatWindow() {
+ const previousVisibleUnfoldedChatWindow =
+ this._getNextVisibleUnfoldedChatWindow({ reverse: true });
+ if (previousVisibleUnfoldedChatWindow) {
+ previousVisibleUnfoldedChatWindow.focus();
+ }
+ }
+
+ /**
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.notifyServer]
+ */
+ fold({ notifyServer } = {}) {
+ if (notifyServer === undefined) {
+ notifyServer = !this.env.messaging.device.isMobile;
+ }
+ this.update({ isFolded: true });
+ // Flux specific: manually folding the chat window should save the
+ // new state on the server.
+ if (this.thread && notifyServer) {
+ this.thread.notifyFoldStateToServer('folded');
+ }
+ }
+
+ /**
+ * Makes this chat window active, which consists of making it visible,
+ * unfolding it, and focusing it.
+ *
+ * @param {Object} [options]
+ */
+ makeActive(options) {
+ this.makeVisible();
+ this.unfold(options);
+ this.focus();
+ }
+
+ /**
+ * Makes this chat window visible by swapping it with the last visible
+ * chat window, or do nothing if it is already visible.
+ */
+ makeVisible() {
+ if (this.isVisible) {
+ return;
+ }
+ const lastVisible = this.manager.lastVisible;
+ this.manager.swap(this, lastVisible);
+ }
+
+ /**
+ * Shift this chat window to the left on screen.
+ */
+ shiftLeft() {
+ this.manager.shiftLeft(this);
+ }
+
+ /**
+ * Shift this chat window to the right on screen.
+ */
+ shiftRight() {
+ this.manager.shiftRight(this);
+ }
+
+ /**
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.notifyServer]
+ */
+ unfold({ notifyServer } = {}) {
+ if (notifyServer === undefined) {
+ notifyServer = !this.env.messaging.device.isMobile;
+ }
+ this.update({ isFolded: false });
+ // Flux specific: manually opening the chat window should save the
+ // new state on the server.
+ if (this.thread && notifyServer) {
+ this.thread.notifyFoldStateToServer('open');
+ }
+ }
+
+ //----------------------------------------------------------------------
+ // Private
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasNewMessageForm() {
+ return this.isVisible && !this.isFolded && !this.thread;
+ }
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasShiftLeft() {
+ if (!this.manager) {
+ return false;
+ }
+ const allVisible = this.manager.allOrderedVisible;
+ const index = allVisible.findIndex(visible => visible === this);
+ if (index === -1) {
+ return false;
+ }
+ return index < allVisible.length - 1;
+ }
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasShiftRight() {
+ if (!this.manager) {
+ return false;
+ }
+ const index = this.manager.allOrderedVisible.findIndex(visible => visible === this);
+ if (index === -1) {
+ return false;
+ }
+ return index > 0;
+ }
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeHasThreadView() {
+ return this.isVisible && !this.isFolded && this.thread;
+ }
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeIsFolded() {
+ const thread = this.thread;
+ if (thread) {
+ return thread.foldState === 'folded';
+ }
+ return this.isFolded;
+ }
+
+ /**
+ * @private
+ * @returns {boolean}
+ */
+ _computeIsVisible() {
+ if (!this.manager) {
+ return false;
+ }
+ return this.manager.allOrderedVisible.includes(this);
+ }
+
+ /**
+ * @private
+ * @returns {string}
+ */
+ _computeName() {
+ if (this.thread) {
+ return this.thread.displayName;
+ }
+ return this.env._t("New message");
+ }
+
+ /**
+ * @private
+ * @returns {integer|undefined}
+ */
+ _computeVisibleIndex() {
+ if (!this.manager) {
+ return clear();
+ }
+ const visible = this.manager.visual.visible;
+ const index = visible.findIndex(visible => visible.chatWindowLocalId === this.localId);
+ if (index === -1) {
+ return clear();
+ }
+ return index;
+ }
+
+ /**
+ * @private
+ * @returns {integer}
+ */
+ _computeVisibleOffset() {
+ if (!this.manager) {
+ return 0;
+ }
+ const visible = this.manager.visual.visible;
+ const index = visible.findIndex(visible => visible.chatWindowLocalId === this.localId);
+ if (index === -1) {
+ return 0;
+ }
+ return visible[index].offset;
+ }
+
+ /**
+ * Cycles to the next possible visible and unfolded chat window starting
+ * from the `currentChatWindow`, following the natural order based on the
+ * current text direction, and with the possibility to `reverse` based on
+ * the given parameter.
+ *
+ * @private
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.reverse=false]
+ * @returns {mail.chat_window|undefined}
+ */
+ _getNextVisibleUnfoldedChatWindow({ reverse = false } = {}) {
+ const orderedVisible = this.manager.allOrderedVisible;
+ /**
+ * Return index of next visible chat window of a given visible chat
+ * window index. The direction of "next" chat window depends on
+ * `reverse` option.
+ *
+ * @param {integer} index
+ * @returns {integer}
+ */
+ const _getNextIndex = index => {
+ const directionOffset = reverse ? 1 : -1;
+ let nextIndex = index + directionOffset;
+ if (nextIndex > orderedVisible.length - 1) {
+ nextIndex = 0;
+ }
+ if (nextIndex < 0) {
+ nextIndex = orderedVisible.length - 1;
+ }
+ return nextIndex;
+ };
+
+ const currentIndex = orderedVisible.findIndex(visible => visible === this);
+ let nextIndex = _getNextIndex(currentIndex);
+ let nextToFocus = orderedVisible[nextIndex];
+ while (nextToFocus.isFolded) {
+ nextIndex = _getNextIndex(nextIndex);
+ nextToFocus = orderedVisible[nextIndex];
+ }
+ return nextToFocus;
+ }
+
+ //----------------------------------------------------------------------
+ // Handlers
+ //----------------------------------------------------------------------
+
+ /**
+ * @private
+ */
+ async _onHideHomeMenu() {
+ if (!this.threadView) {
+ return;
+ }
+ this.threadView.addComponentHint('home-menu-hidden');
+ }
+
+ /**
+ * @private
+ */
+ async _onShowHomeMenu() {
+ if (!this.threadView) {
+ return;
+ }
+ this.threadView.addComponentHint('home-menu-shown');
+ }
+
+ }
+
+ ChatWindow.fields = {
+ /**
+ * Determines whether "new message form" should be displayed.
+ */
+ hasNewMessageForm: attr({
+ compute: '_computeHasNewMessageForm',
+ dependencies: [
+ 'isFolded',
+ 'isVisible',
+ 'thread',
+ ],
+ }),
+ hasShiftLeft: attr({
+ compute: '_computeHasShiftLeft',
+ dependencies: ['managerAllOrderedVisible'],
+ default: false,
+ }),
+ hasShiftRight: attr({
+ compute: '_computeHasShiftRight',
+ dependencies: ['managerAllOrderedVisible'],
+ default: false,
+ }),
+ /**
+ * Determines whether `this.thread` should be displayed.
+ */
+ hasThreadView: attr({
+ compute: '_computeHasThreadView',
+ dependencies: [
+ 'isFolded',
+ 'isVisible',
+ 'thread',
+ ],
+ }),
+ /**
+ * Determine whether the chat window should be programmatically
+ * focused by observed component of chat window. Those components
+ * are responsible to unmark this record afterwards, otherwise
+ * any re-render will programmatically set focus again!
+ */
+ isDoFocus: attr({
+ default: false,
+ }),
+ /**
+ * States whether `this` is focused. Useful for visual clue.
+ */
+ isFocused: attr({
+ default: false,
+ }),
+ /**
+ * Determines whether `this` is folded.
+ */
+ isFolded: attr({
+ default: false,
+ }),
+ /**
+ * States whether `this` is visible or not. Should be considered
+ * read-only. Setting this value manually will not make it visible.
+ * @see `makeVisible`
+ */
+ isVisible: attr({
+ compute: '_computeIsVisible',
+ dependencies: [
+ 'managerAllOrderedVisible',
+ ],
+ }),
+ manager: many2one('mail.chat_window_manager', {
+ inverse: 'chatWindows',
+ }),
+ managerAllOrderedVisible: one2many('mail.chat_window', {
+ related: 'manager.allOrderedVisible',
+ }),
+ managerVisual: attr({
+ related: 'manager.visual',
+ }),
+ name: attr({
+ compute: '_computeName',
+ dependencies: [
+ 'thread',
+ 'threadDisplayName',
+ ],
+ }),
+ /**
+ * Determines the `mail.thread` that should be displayed by `this`.
+ * If no `mail.thread` is linked, `this` is considered "new message".
+ */
+ thread: one2one('mail.thread', {
+ inverse: 'chatWindow',
+ }),
+ threadDisplayName: attr({
+ related: 'thread.displayName',
+ }),
+ /**
+ * States the `mail.thread_view` displaying `this.thread`.
+ */
+ threadView: one2one('mail.thread_view', {
+ related: 'threadViewer.threadView',
+ }),
+ /**
+ * Determines the `mail.thread_viewer` managing the display of `this.thread`.
+ */
+ threadViewer: one2one('mail.thread_viewer', {
+ default: [['create']],
+ inverse: 'chatWindow',
+ isCausal: true,
+ }),
+ /**
+ * This field handle the "order" (index) of the visible chatWindow inside the UI.
+ *
+ * Using LTR, the right-most chat window has index 0, and the number is incrementing from right to left.
+ * Using RTL, the left-most chat window has index 0, and the number is incrementing from left to right.
+ */
+ visibleIndex: attr({
+ compute: '_computeVisibleIndex',
+ dependencies: [
+ 'manager',
+ 'managerVisual',
+ ],
+ }),
+ visibleOffset: attr({
+ compute: '_computeVisibleOffset',
+ dependencies: ['managerVisual'],
+ }),
+ };
+
+ ChatWindow.modelName = 'mail.chat_window';
+
+ return ChatWindow;
+}
+
+registerNewModel('mail.chat_window', factory);
+
+});