From 5a958cb3ff2d0826edbb10b8b31619ad47a47e8a Mon Sep 17 00:00:00 2001 From: "Didier (did)" Date: Fri, 7 Jan 2022 14:07:30 +0000 Subject: [PATCH] [IMP] mail: move fileUploader methods in the model view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task-2579306 closes odoo/odoo#81848 Signed-off-by: Sébastien Theys (seb) --- .../attachment_box/attachment_box.js | 2 +- .../src/components/composer/composer.js | 6 +- .../components/file_uploader/file_uploader.js | 154 +----------------- .../file_uploader/file_uploader.xml | 4 +- .../src/models/activity_view/activity_view.js | 2 +- .../attachment_box_view.js | 2 +- .../file_uploader_view/file_uploader_view.js | 143 +++++++++++++++- 7 files changed, 150 insertions(+), 163 deletions(-) diff --git a/addons/mail/static/src/components/attachment_box/attachment_box.js b/addons/mail/static/src/components/attachment_box/attachment_box.js index b6796db48ac..88a91a4a900 100644 --- a/addons/mail/static/src/components/attachment_box/attachment_box.js +++ b/addons/mail/static/src/components/attachment_box/attachment_box.js @@ -39,7 +39,7 @@ export class AttachmentBox extends Component { * @param {FileList} detail.files */ async _onDropZoneFilesDropped(detail) { - await this.attachmentBoxView.fileUploaderView.component.uploadFiles(detail.files); + await this.attachmentBoxView.fileUploaderView.uploadFiles(detail.files); this.isDropZoneVisible.value = false; } diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 37a4bbd9b48..fed5d4227db 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -122,7 +122,7 @@ export class Composer extends Component { * @private */ _onClickAddAttachment() { - this.composerView.fileUploaderView.component.openBrowserFileUploader(); + this.composerView.fileUploaderView.openBrowserFileUploader(); if (!this.messaging.device.isMobileDevice) { this.composerView.update({ doFocus: true }); } @@ -172,7 +172,7 @@ export class Composer extends Component { * @param {FileList} detail.files */ async _onDropZoneFilesDropped(detail) { - await this.composerView.fileUploaderView.component.uploadFiles(detail.files); + await this.composerView.fileUploaderView.uploadFiles(detail.files); this.isDropZoneVisible.value = false; } @@ -201,7 +201,7 @@ export class Composer extends Component { if (!ev.clipboardData || !ev.clipboardData.files) { return; } - await this.composerView.fileUploaderView.component.uploadFiles(ev.clipboardData.files); + await this.composerView.fileUploaderView.uploadFiles(ev.clipboardData.files); } } diff --git a/addons/mail/static/src/components/file_uploader/file_uploader.js b/addons/mail/static/src/components/file_uploader/file_uploader.js index 1f4de9c67aa..d8caa483e45 100644 --- a/addons/mail/static/src/components/file_uploader/file_uploader.js +++ b/addons/mail/static/src/components/file_uploader/file_uploader.js @@ -1,21 +1,9 @@ /** @odoo-module **/ -import { useComponentToModel } from '@mail/component_hooks/use_component_to_model/use_component_to_model'; +import { useRefToModel } from '@mail/component_hooks/use_ref_to_model/use_ref_to_model'; import { registerMessagingComponent } from '@mail/utils/messaging_component'; -import { replace } from '@mail/model/model_field_command'; - -import core from 'web.core'; const { Component } = owl; -const { useRef } = owl.hooks; - -const geAttachmentNextTemporaryId = (function () { - let tmpId = 0; - return () => { - tmpId -= 1; - return tmpId; - }; -})(); export class FileUploader extends Component { @@ -24,9 +12,7 @@ export class FileUploader extends Component { */ setup() { super.setup(); - useComponentToModel({ fieldName: 'component', modelName: 'FileUploaderView', propNameAsRecordLocalId: 'localId' }); - this._fileInputRef = useRef('fileInput'); - this._fileUploadId = _.uniqueId('o_FileUploader_fileupload'); + useRefToModel({ fieldName: 'fileInputRef', modelName: 'FileUploaderView', propNameAsRecordLocalId: 'localId', refName: 'fileInput' }); } //-------------------------------------------------------------------------- @@ -37,142 +23,6 @@ export class FileUploader extends Component { return this.messaging && this.messaging.models['FileUploaderView'].get(this.props.localId); } - /** - * @param {FileList|Array} files - * @returns {Promise} - */ - async uploadFiles(files) { - await this._performUpload({ files }); - this._fileInputRef.el.value = ''; - } - - openBrowserFileUploader() { - this._fileInputRef.el.click(); - } - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {Object} param0 - * @param {Composer} param0.composer - * @param {File} param0.file - * @param {Thread} param0.thread - * @returns {FormData} - */ - _createFormData({ composer, file, thread }) { - const formData = new window.FormData(); - formData.append('csrf_token', core.csrf_token); - formData.append('is_pending', Boolean(composer)); - formData.append('thread_id', thread && thread.id); - formData.append('thread_model', thread && thread.model); - formData.append('ufile', file, file.name); - return formData; - } - - /** - * @private - * @param {Object} param0 - * @param {FileList|Array} param0.files - * @returns {Promise} - */ - async _performUpload({ files }) { - const composer = this.fileUploaderView.composerView && this.fileUploaderView.composerView.composer; // save before async - const thread = this.fileUploaderView.thread; // save before async - const chatter = this.fileUploaderView.attachmentBoxView && this.fileUploaderView.attachmentBoxView.chatter; // save before async - const activity = this.fileUploaderView.activityView && this.fileUploaderView.activityView.activity; // save before async - const uploadingAttachments = new Map(); - for (const file of files) { - uploadingAttachments.set(file, this.messaging.models['Attachment'].insert({ - composer: composer && replace(composer), - filename: file.name, - id: geAttachmentNextTemporaryId(), - isUploading: true, - mimetype: file.type, - name: file.name, - originThread: (!composer && thread) ? replace(thread) : undefined, - })); - } - const attachments = []; - for (const file of files) { - const uploadingAttachment = uploadingAttachments.get(file); - if (!uploadingAttachment.exists()) { - // This happens when a pending attachment is being deleted by user before upload. - continue; - } - if ((composer && !composer.exists()) || (thread && !thread.exists())) { - return; - } - try { - const response = await this.env.browser.fetch('/mail/attachment/upload', { - method: 'POST', - body: this._createFormData({ composer, file, thread }), - signal: uploadingAttachment.uploadingAbortController.signal, - }); - const attachmentData = await response.json(); - if (uploadingAttachment.exists()) { - uploadingAttachment.delete(); - } - if ((composer && !composer.exists()) || (thread && !thread.exists())) { - return; - } - const attachment = this._onAttachmentUploaded({ attachmentData, composer, thread }); - attachments.push(attachment); - } catch (e) { - if (e.name !== 'AbortError') { - throw e; - } - } - } - if (chatter && chatter.exists() && chatter.hasParentReloadOnAttachmentsChanged) { - chatter.reloadParentView(); - } - if (activity && activity.exists()) { - activity.markAsDone({ attachments }); - } - } - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - * @param {Object} param0 - * @param {Object} param0.attachmentData - * @param {Composer} param0.composer - * @param {Thread} param0.thread - * @returns {Attachment} - */ - _onAttachmentUploaded({ attachmentData, composer, thread }) { - if (attachmentData.error || !attachmentData.id) { - this.env.services['notification'].notify({ - type: 'danger', - message: attachmentData.error, - }); - return; - } - return this.messaging.models['Attachment'].insert({ - composer: composer && replace(composer), - originThread: (!composer && thread) ? replace(thread) : undefined, - ...attachmentData, - }); - } - - /** - * Called when there are changes in the file input. - * - * @private - * @param {Event} ev - * @param {EventTarget} ev.target - * @param {FileList|Array} ev.target.files - */ - async _onChangeAttachment(ev) { - await this.uploadFiles(ev.target.files); - } - } Object.assign(FileUploader, { diff --git a/addons/mail/static/src/components/file_uploader/file_uploader.xml b/addons/mail/static/src/components/file_uploader/file_uploader.xml index 81bb1e0f6ac..b2d61a302f9 100644 --- a/addons/mail/static/src/components/file_uploader/file_uploader.xml +++ b/addons/mail/static/src/components/file_uploader/file_uploader.xml @@ -3,7 +3,9 @@
- + + +
diff --git a/addons/mail/static/src/models/activity_view/activity_view.js b/addons/mail/static/src/models/activity_view/activity_view.js index 20a5176ec82..f81857be522 100644 --- a/addons/mail/static/src/models/activity_view/activity_view.js +++ b/addons/mail/static/src/models/activity_view/activity_view.js @@ -61,7 +61,7 @@ registerModel({ * explorer for upload. */ onClickUploadDocument() { - this.fileUploaderView.component.openBrowserFileUploader(); + this.fileUploaderView.openBrowserFileUploader(); }, /** * @private diff --git a/addons/mail/static/src/models/attachment_box_view/attachment_box_view.js b/addons/mail/static/src/models/attachment_box_view/attachment_box_view.js index b0f0d277def..a51b85fe063 100644 --- a/addons/mail/static/src/models/attachment_box_view/attachment_box_view.js +++ b/addons/mail/static/src/models/attachment_box_view/attachment_box_view.js @@ -25,7 +25,7 @@ registerModel({ * Handles click on the "add attachment" button. */ onClickAddAttachment() { - this.fileUploaderView.component.openBrowserFileUploader(); + this.fileUploaderView.openBrowserFileUploader(); }, }, fields: { diff --git a/addons/mail/static/src/models/file_uploader_view/file_uploader_view.js b/addons/mail/static/src/models/file_uploader_view/file_uploader_view.js index 09d9015bc8b..d4ca79fe79d 100644 --- a/addons/mail/static/src/models/file_uploader_view/file_uploader_view.js +++ b/addons/mail/static/src/models/file_uploader_view/file_uploader_view.js @@ -4,10 +4,48 @@ import { registerModel } from '@mail/model/model_core'; import { attr, many2one, one2one } from '@mail/model/model_field'; import { clear, replace } from '@mail/model/model_field_command'; +import core from 'web.core'; + +const getAttachmentNextTemporaryId = (function () { + let tmpId = 0; + return () => { + tmpId -= 1; + return tmpId; + }; +})(); + registerModel({ name: 'FileUploaderView', identifyingFields: [['activityView', 'attachmentBoxView', 'composerView']], + lifecycleHooks: { + _created() { + this.onChangeAttachment = this.onChangeAttachment.bind(this); + } + }, recordMethods: { + openBrowserFileUploader() { + this.fileInputRef.el.click(); + }, + /** + * Called when there are changes in the file input. + * + * @param {Event} ev + * @param {EventTarget} ev.target + * @param {FileList|Array} ev.target.files + */ + onChangeAttachment(ev) { + this.uploadFiles(ev.target.files); + }, + /** + * @param {FileList|Array} files + * @returns {Promise} + */ + async uploadFiles(files) { + await this._performUpload({ files }); + if (this.fileInputRef && this.fileInputRef.el) { + this.fileInputRef.el.value = ''; + } + }, /** * @private * @returns {FieldCommand} @@ -24,6 +62,106 @@ registerModel({ } return clear(); }, + /** + * @private + * @param {Object} param0 + * @param {Composer} param0.composer + * @param {File} param0.file + * @param {Thread} param0.thread + * @returns {FormData} + */ + _createFormData({ composer, file, thread }) { + const formData = new window.FormData(); + formData.append('csrf_token', core.csrf_token); + formData.append('is_pending', Boolean(composer)); + formData.append('thread_id', thread && thread.id); + formData.append('thread_model', thread && thread.model); + formData.append('ufile', file, file.name); + return formData; + }, + /** + * @private + * @param {Object} param0 + * @param {Object} param0.attachmentData + * @param {Composer} param0.composer + * @param {Thread} param0.thread + * @returns {Attachment} + */ + _onAttachmentUploaded({ attachmentData, composer, thread }) { + if (attachmentData.error || !attachmentData.id) { + this.env.services['notification'].notify({ + type: 'danger', + message: attachmentData.error, + }); + return; + } + return this.messaging.models['Attachment'].insert({ + composer: composer && replace(composer), + originThread: (!composer && thread) ? replace(thread) : undefined, + ...attachmentData, + }); + }, + /** + * @private + * @param {Object} param0 + * @param {FileList|Array} param0.files + * @returns {Promise} + */ + async _performUpload({ files }) { + const composer = this.composerView && this.composerView.composer; // save before async + const thread = this.thread; // save before async + const chatter = this.attachmentBoxView && this.attachmentBoxView.chatter; // save before async + const activity = this.activityView && this.activityView.activity; // save before async + const uploadingAttachments = new Map(); + for (const file of files) { + uploadingAttachments.set(file, this.messaging.models['Attachment'].insert({ + composer: composer && replace(composer), + filename: file.name, + id: getAttachmentNextTemporaryId(), + isUploading: true, + mimetype: file.type, + name: file.name, + originThread: (!composer && thread) ? replace(thread) : undefined, + })); + } + const attachments = []; + for (const file of files) { + const uploadingAttachment = uploadingAttachments.get(file); + if (!uploadingAttachment.exists()) { + // This happens when a pending attachment is being deleted by user before upload. + continue; + } + if ((composer && !composer.exists()) || (thread && !thread.exists())) { + return; + } + try { + const response = await this.env.browser.fetch('/mail/attachment/upload', { + method: 'POST', + body: this._createFormData({ composer, file, thread }), + signal: uploadingAttachment.uploadingAbortController.signal, + }); + const attachmentData = await response.json(); + if (uploadingAttachment.exists()) { + uploadingAttachment.delete(); + } + if ((composer && !composer.exists()) || (thread && !thread.exists())) { + return; + } + const attachment = this._onAttachmentUploaded({ attachmentData, composer, thread }); + attachments.push(attachment); + } catch (e) { + if (e.name !== 'AbortError') { + throw e; + } + } + } + if (chatter && chatter.exists() && chatter.hasParentReloadOnAttachmentsChanged) { + chatter.reloadParentView(); + } + if (activity && activity.exists()) { + activity.markAsDone({ attachments }); + } + }, }, fields: { activityView: one2one('ActivityView', { @@ -36,15 +174,12 @@ registerModel({ isCausal: true, readonly: true, }), - /** - * States the OWL component of this file uploader view. - */ - component: attr(), composerView: one2one('ComposerView', { inverse: 'fileUploaderView', isCausal: true, readonly: true, }), + fileInputRef: attr(), thread: many2one('Thread', { compute: '_computeThread', readonly: true,