From 8904dbd522ee859e0b4cf58125b6aa4cbe285d1c Mon Sep 17 00:00:00 2001 From: "Xavier BOL (xbo)" Date: Mon, 10 Oct 2022 14:11:26 +0200 Subject: [PATCH] [REF] project: convert project sharing form view in OWL Before this commit, the form view in project sharing was always in legacy. Moreover, in that view, the chatter used is the portal one and so it is also a legacy widget. This commit converts the form view and then convert the chatter in OWL to be able to correctly compile and add the chatter in the form view. Also, some styling has been done to remove the overflow. Only the overflow on the y axis in the chatter can be scrolled on the page. task-2947516 X-original-commit: 175430f3593337ce582cbec644b9d630894c074b --- addons/project/__manifest__.py | 11 +- .../src/project_sharing/components/chatter.js | 34 ---- .../project_sharing/components/chatter.xml | 8 - .../components/chatter}/chatter.scss | 7 +- .../chatter/chatter_attachments_viewer.js | 15 ++ .../chatter/chatter_attachments_viewer.xml | 28 +++ .../components/chatter/chatter_composer.js | 134 ++++++++++++++ .../components/chatter/chatter_composer.xml | 61 +++++++ .../components/chatter/chatter_container.js | 172 ++++++++++++++++++ .../components/chatter/chatter_container.xml | 27 +++ .../chatter/chatter_message_counter.js | 10 + .../chatter/chatter_message_counter.xml | 16 ++ .../components/chatter/chatter_messages.js | 37 ++++ .../components/chatter/chatter_messages.xml | 43 +++++ .../components/chatter/chatter_pager.js | 64 +++++++ .../components/chatter/chatter_pager.xml | 21 +++ .../project_sharing/components/composer.js | 17 -- .../portal_attach_document.js | 33 ++++ .../portal_attach_document.xml | 21 +++ .../portal_file_input/portal_file_input.js | 46 +++++ .../project_sharing/views/form/controller.js | 8 - .../form/project_sharing_form_compiler.js | 105 +++++++++++ .../form/project_sharing_form_controller.js | 31 ++++ .../form/project_sharing_form_controller.xml | 12 ++ .../form/project_sharing_form_renderer.js | 10 + .../views/form/project_sharing_form_view.js | 8 + .../project_sharing/views/form/renderer.js | 52 ------ .../src/project_sharing/views/form/view.js | 12 -- .../src/project_sharing/views/registry.js | 15 -- .../src/scss/project_sharing_frontend.scss | 3 + .../tests/tours/project_sharing_tour.js | 6 +- .../project/tests/test_project_sharing_ui.py | 2 +- .../views/project_sharing_templates.xml | 4 +- .../project/views/project_sharing_views.xml | 2 +- 34 files changed, 912 insertions(+), 163 deletions(-) delete mode 100644 addons/project/static/src/project_sharing/components/chatter.js delete mode 100644 addons/project/static/src/project_sharing/components/chatter.xml rename addons/project/static/src/{scss/project_sharing => project_sharing/components/chatter}/chatter.scss (89%) create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.xml create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_composer.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_composer.xml create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_container.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_container.xml create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.xml create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_messages.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_messages.xml create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_pager.js create mode 100644 addons/project/static/src/project_sharing/components/chatter/chatter_pager.xml delete mode 100644 addons/project/static/src/project_sharing/components/composer.js create mode 100644 addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.js create mode 100644 addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.xml create mode 100644 addons/project/static/src/project_sharing/components/portal_file_input/portal_file_input.js delete mode 100644 addons/project/static/src/project_sharing/views/form/controller.js create mode 100644 addons/project/static/src/project_sharing/views/form/project_sharing_form_compiler.js create mode 100644 addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.js create mode 100644 addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.xml create mode 100644 addons/project/static/src/project_sharing/views/form/project_sharing_form_renderer.js create mode 100644 addons/project/static/src/project_sharing/views/form/project_sharing_form_view.js delete mode 100644 addons/project/static/src/project_sharing/views/form/renderer.js delete mode 100644 addons/project/static/src/project_sharing/views/form/view.js delete mode 100644 addons/project/static/src/project_sharing/views/registry.js create mode 100644 addons/project/static/src/scss/project_sharing_frontend.scss diff --git a/addons/project/__manifest__.py b/addons/project/__manifest__.py index c231378b70b..a91b3c10c8e 100644 --- a/addons/project/__manifest__.py +++ b/addons/project/__manifest__.py @@ -79,6 +79,7 @@ ], 'web.assets_frontend': [ 'project/static/src/scss/portal_rating.scss', + 'project/static/src/scss/project_sharing_frontend.scss', 'project/static/src/js/portal_rating.js', ], 'web.qunit_suite_tests': [ @@ -95,18 +96,12 @@ ('remove', 'mail/static/src/components/dialog_manager/dialog_manager.js'), ('remove', 'mail/static/src/components/chat_window_manager/chat_window_manager.js'), + ('remove', 'mail/static/src/views/form/*'), - 'web/static/src/legacy/js/public/public_widget.js', - 'portal/static/src/js/portal_chatter.js', - 'portal/static/src/js/portal_composer.js', 'project/static/src/project_sharing/search/favorite_menu/custom_favorite_item.xml', - 'project/static/src/project_sharing/**/*.js', - 'project/static/src/scss/project_sharing/*', + 'project/static/src/project_sharing/**/*', 'web/static/src/start.js', 'web/static/src/legacy/legacy_setup.js', - - 'project/static/src/project_sharing/**/*.xml', - 'portal/static/src/xml/portal_chatter.xml', ], }, 'license': 'LGPL-3', diff --git a/addons/project/static/src/project_sharing/components/chatter.js b/addons/project/static/src/project_sharing/components/chatter.js deleted file mode 100644 index e1ad0d48a25..00000000000 --- a/addons/project/static/src/project_sharing/components/chatter.js +++ /dev/null @@ -1,34 +0,0 @@ -/** @odoo-module **/ - -import { PortalChatter } from 'portal.chatter'; -import Composer from './composer'; - -export default PortalChatter.extend({ - messageFetch(domain) { - if (!this.options.res_id) { - return Promise.resolve(); - } - return this._super.apply(this, arguments); - }, - _chatterInit() { - if (!this.options.res_id) { - this.result = { messages: [] }; - return Promise.resolve(); - } - return this._super.apply(this, arguments); - }, - _messageFetchPrepareParams() { - const data = this._super.apply(this, arguments); - if (this.options.project_sharing_id) { - data.project_sharing_id = this.options.project_sharing_id; - } - return data; - }, - _createComposerWidget() { - return new Composer(this, this.options); - }, - update(props) { - this._setOptions(props); - this._reloadChatterContent(); - }, -}); diff --git a/addons/project/static/src/project_sharing/components/chatter.xml b/addons/project/static/src/project_sharing/components/chatter.xml deleted file mode 100644 index aca16ce257a..00000000000 --- a/addons/project/static/src/project_sharing/components/chatter.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - !!widget.options.res_id && widget.get('messages') || [] - - - diff --git a/addons/project/static/src/scss/project_sharing/chatter.scss b/addons/project/static/src/project_sharing/components/chatter/chatter.scss similarity index 89% rename from addons/project/static/src/scss/project_sharing/chatter.scss rename to addons/project/static/src/project_sharing/components/chatter/chatter.scss index 88f1d1262b4..745a2c40b54 100644 --- a/addons/project/static/src/scss/project_sharing/chatter.scss +++ b/addons/project/static/src/project_sharing/components/chatter/chatter.scss @@ -19,11 +19,12 @@ .o_portal_chatter_attachments { margin-bottom: 1rem; .o_portal_chatter_attachment { - .o_portal_chatter_attachment_delete { + > button { @include o-position-absolute($top: 0, $right: 0); opacity: 0; } - &:hover .o_portal_chatter_attachment_delete { + + &:hover > button { opacity: 1; } } @@ -55,7 +56,7 @@ .o_portal_chatter { .o_portal_chatter_header { padding-top: 1rem; - padding-bottom: 2px; + padding-bottom: 1px; } .o_portal_chatter_composer, .o_portal_chatter_messages { diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.js b/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.js new file mode 100644 index 00000000000..ffb44832a63 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.js @@ -0,0 +1,15 @@ +/** @odoo-module */ + +const { Component } = owl; + +export class ChatterAttachmentsViewer extends Component {} + +ChatterAttachmentsViewer.template = 'project.ChatterAttachmentsViewer'; +ChatterAttachmentsViewer.props = { + attachments: Array, + canDelete: { type: Boolean, optional: true }, + delete: { type: Function, optional: true }, +}; +ChatterAttachmentsViewer.defaultProps = { + delete: async () => {}, +}; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.xml new file mode 100644 index 00000000000..691424bb4bd --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_attachments_viewer.xml @@ -0,0 +1,28 @@ + + + +
+
+
+
+ + + +
+
+
+ + + diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_composer.js b/addons/project/static/src/project_sharing/components/chatter/chatter_composer.js new file mode 100644 index 00000000000..f4219ea8acb --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_composer.js @@ -0,0 +1,134 @@ +/** @odoo-module */ + +import { useService } from '@web/core/utils/hooks'; +import { TextField } from '@web/views/fields/text/text_field'; +import { PortalAttachDocument } from '../portal_attach_document/portal_attach_document'; +import { ChatterAttachmentsViewer } from './chatter_attachments_viewer'; + +const { Component, useState, onWillUpdateProps } = owl; + +export class ChatterComposer extends Component { + setup() { + this.rpc = useService('rpc'); + this.state = useState({ + displayError: false, + attachments: this.props.attachments.map(file => file.state === 'done'), + message: '', + loading: false, + }); + + onWillUpdateProps(this.onWillUpdateProps); + } + + onWillUpdateProps(nextProps) { + this.clearErrors(); + this.state.message = ''; + this.state.attachments = nextProps.attachments.map(file => file.state === 'done'); + } + + get discussionUrl() { + return `${window.location.href.split('#')[0]}#discussion`; + } + + update(change) { + this.clearErrors(); + this.state.message = change; + } + + prepareMessageData() { + const attachment_ids = []; + const attachment_tokens = []; + for (const attachment of this.state.attachments) { + attachment_ids.push(attachment.id); + attachment_tokens.push(attachment.access_token); + } + return { + message: this.state.message, + attachment_ids, + attachment_tokens, + res_model: this.props.resModel, + res_id: this.props.resId, + project_sharing_id: this.props.projectSharingId, + }; + } + + async sendMessage() { + this.clearErrors(); + if (!this.state.message && !this.state.attachments.length) { + this.state.displayError = true; + return; + } + + await this.rpc( + "/mail/chatter_post", + this.prepareMessageData(), + ); + this.props.postProcessMessageSent(); + this.state.message = ""; + this.state.attachments = []; + } + + clearErrors() { + this.state.displayError = false; + } + + async beforeUploadFile() { + this.state.loading = true; + return true; + } + + onFileUpload(files) { + this.state.loading = false; + this.clearErrors(); + for (const file of files) { + file.state = 'pending'; + this.state.attachments.push(file); + } + } + + async deleteAttachment(attachment) { + this.clearErrors(); + try { + await this.rpc( + '/portal/attachment/remove', + { + attachment_id: attachment.id, + access_token: attachment.access_token, + }, + ); + } catch (err) { + console.error(err); + this.state.displayError = true; + } + this.state.attachments = this.state.attachments.filter(a => a.id !== attachment.id); + } +} + +ChatterComposer.components = { + ChatterAttachmentsViewer, + PortalAttachDocument, + TextField, +}; + +ChatterComposer.props = { + resModel: String, + projectSharingId: Number, + resId: { type: Number, optional: true }, + allowComposer: { type: Boolean, optional: true }, + displayComposer: { type: Boolean, optional: true }, + token: { type: String, optional: true }, + messageCount: { type: Number, optional: true }, + isUserPublic: { type: Boolean, optional: true }, + partnerId: { type: Number, optional: true }, + postProcessMessageSent: { type: Function, optional: true }, + attachments: { type: Array, optional: true }, +}; +ChatterComposer.defaultProps = { + allowComposer: true, + displayComposer: false, + isUserPublic: true, + token: '', + attachments: [], +}; + +ChatterComposer.template = 'project.ChatterComposer'; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_composer.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_composer.xml new file mode 100644 index 00000000000..6fda276a2a4 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_composer.xml @@ -0,0 +1,61 @@ + + + + +
+ + + +
+ Avatar +
+
+
+ + +
+ + + + +
+
+
+
+
+
+ +

Leave a comment

+

You must be logged in to post a comment.

+
+
+
+ +
diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_container.js b/addons/project/static/src/project_sharing/components/chatter/chatter_container.js new file mode 100644 index 00000000000..fc2e64a723d --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_container.js @@ -0,0 +1,172 @@ +/** @odoo-module */ + +import { formatDateTime, parseDateTime } from "@web/core/l10n/dates"; +import { useService } from "@web/core/utils/hooks"; +import { sprintf } from '@web/core/utils/strings'; +import { ChatterComposer } from "./chatter_composer"; +import { ChatterMessageCounter } from "./chatter_message_counter"; +import { ChatterMessages } from "./chatter_messages"; +import { ChatterPager } from "./chatter_pager"; + +const { Component, markup, onWillStart, useState, onWillUpdateProps } = owl; + +export class ChatterContainer extends Component { + setup() { + this.rpc = useService('rpc'); + this.state = useState({ + currentPage: this.props.pagerStart, + messages: [], + options: this.defaultOptions, + }); + + onWillStart(this.onWillStart); + onWillUpdateProps(this.onWillUpdateProps); + } + + get defaultOptions() { + return { + message_count: 0, + is_user_public: true, + is_user_employee: false, + is_user_published: false, + display_composer: Boolean(this.props.resId), + partner_id: null, + pager_scope: 4, + pager_step: 10, + }; + } + + get options() { + return this.state.options; + } + + set options(options) { + this.state.options = { + ...this.defaultOptions, + ...options, + display_composer: !!options.display_composer, + access_token: typeof options.display_composer === 'string' ? options.display_composer : '', + }; + } + + get composerProps() { + return { + allowComposer: Boolean(this.props.resId), + displayComposer: this.state.options.display_composer, + partnerId: this.state.options.partner_id || undefined, + token: this.state.options.access_token, + resModel: this.props.resModel, + resId: this.props.resId, + projectSharingId: this.props.projectSharingId, + postProcessMessageSent: async () => { + this.state.currentPage = 1; + await this.fetchMessages(); + }, + attachments: this.state.options.default_attachment_ids, + }; + } + + onWillStart() { + this.initChatter(this.messagesParams(this.props)); + } + + onWillUpdateProps(nextProps) { + if (nextProps.resId) { + this.initChatter(this.messagesParams(nextProps)); + } else { + this.state.messages = []; + this.options = {}; + } + } + + async onChangePage(page) { + this.state.currentPage = page; + await this.fetchMessages(); + } + + async initChatter(params) { + const chatterData = await this.rpc( + '/mail/chatter_init', + params, + ); + this.state.messages = this.preprocessMessages(chatterData.messages); + this.options = chatterData.options; + } + + async fetchMessages() { + const result = await this.rpc( + '/mail/chatter_fetch', + this.messagesParams(this.props), + ); + this.state.messages = this.preprocessMessages(result.messages); + this.state.options.message_count = result.message_count; + return result; + } + + messagesParams(props) { + const params = { + res_model: props.resModel, + res_id: props.resId, + limit: this.state.options.pager_step, + offset: (this.state.currentPage - 1) * this.state.options.pager_step, + allow_composer: Boolean(props.resId), + project_sharing_id: props.projectSharingId, + }; + if (props.token) { + params.token = props.token; + } + if (props.domain) { + params.domain = props.domain; + } + return params; + } + + preprocessMessages(messages) { + return messages.map(m => ({ + ...m, + author_avatar_url: sprintf('/web/image/mail.message/%s/author_avatar/50x50', m.id), + published_date_str: sprintf( + this.env._t('Published on %s'), + formatDateTime( + parseDateTime( + m.date, + { format: 'MM-dd-yyy HH:mm:ss' }, + ), + ) + ), + body: markup(m.body), + })); + } + + updateMessage(message_id, changes) { + Object.assign( + this.state.messages.find(m => m.id === message_id), + changes, + ); + } +} + +ChatterContainer.components = { + ChatterComposer, + ChatterMessageCounter, + ChatterMessages, + ChatterPager, +}; + +ChatterContainer.props = { + token: { type: String, optional: true }, + resModel: String, + resId: { type: Number, optional: true }, + pid: { type: String, optional: true }, + hash: { type: String, optional: true }, + pagerStart: { type: Number, optional: true }, + twoColumns: { type: Boolean, optional: true }, + projectSharingId: Number, +}; +ChatterContainer.defaultProps = { + token: '', + pid: '', + hash: '', + pagerStart: 1, +}; +ChatterContainer.template = 'project.ChatterContainer'; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_container.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_container.xml new file mode 100644 index 00000000000..35337763f90 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_container.xml @@ -0,0 +1,27 @@ + + + +
+
+
+ +
+
+ +
+
+ + +
+
+
+ +
diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.js b/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.js new file mode 100644 index 00000000000..479aa855623 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.js @@ -0,0 +1,10 @@ +/** @odoo-module */ + +const { Component } = owl; + +export class ChatterMessageCounter extends Component { } + +ChatterMessageCounter.props = { + count: Number, +}; +ChatterMessageCounter.template = 'project.ChatterMessageCounter'; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.xml new file mode 100644 index 00000000000..5451e4ccae4 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_message_counter.xml @@ -0,0 +1,16 @@ + + + +
+ + + + comments + + + There are no comments for now. + +
+
+ +
diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_messages.js b/addons/project/static/src/project_sharing/components/chatter/chatter_messages.js new file mode 100644 index 00000000000..2c0e54824fa --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_messages.js @@ -0,0 +1,37 @@ +/** @odoo-module */ + +import { useService } from "@web/core/utils/hooks"; + +import { ChatterAttachmentsViewer } from "./chatter_attachments_viewer"; + +const { Component } = owl; + +export class ChatterMessages extends Component { + setup() { + this.rpc = useService('rpc'); + } + + /** + * Toggle the visibility of the message. + * + * @param {Object} message message to change the visibility + */ + async toggleMessageVisibility(message) { + const result = await this.rpc( + '/mail/update_is_internal', + { message_id: message.id, is_internal: !message.is_internal }, + ); + this.props.update(message.id, { is_internal: result }); + } +} + +ChatterMessages.template = 'project.ChatterMessages'; +ChatterMessages.props = { + messages: Array, + isUserEmployee: { type: Boolean, optional: true }, + update: { type: Function, optional: true }, +}; +ChatterMessages.defaultProps = { + update: (message_id, changes) => {}, +}; +ChatterMessages.components = { ChatterAttachmentsViewer }; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_messages.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_messages.xml new file mode 100644 index 00000000000..28ae6988e1d --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_messages.xml @@ -0,0 +1,43 @@ + + + +
+ +
+ avatar +
+ +
+ +
+
+ + +
+
+
+
+

+
+ +
+ +
+
+
+
+
+
+ +
diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_pager.js b/addons/project/static/src/project_sharing/components/chatter/chatter_pager.js new file mode 100644 index 00000000000..edc8ffc7899 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_pager.js @@ -0,0 +1,64 @@ +/** @odoo-module */ + +const { Component, useState, onWillUpdateProps } = owl; + +export class ChatterPager extends Component { + setup() { + this.state = useState({ + disabledButtons: false, + pageCount: 1, + pageStart: 1, + pageEnd: 1, + pagePrevious: 1, + pageNext: 1, + pages: [1], + offset: 0, + }); + this.computePagerState(this.props); + + onWillUpdateProps(this.onWillUpdateProps); + } + + computePagerState(props) { + let page = props.page || 1; + let scope = props.pagerScope; + + const step = props.pagerStep; + + // Compute Pager + this.state.messageCount = Math.ceil(parseFloat(props.messageCount) / step); + + page = Math.max(1, Math.min(page, this.state.messageCount)); + + const pageStart = Math.max(page - parseInt(Math.floor(scope / 2)), 1); + this.state.pageEnd = Math.min(pageStart + scope, this.state.messageCount); + this.state.pageStart = Math.max(this.state.pageEnd - scope, 1); + + this.state.pages = Array.from( + {length: this.state.pageEnd - this.state.pageStart + 1}, + (_, i) => i + this.state.pageStart, + ); + this.state.pagePrevious = Math.max(this.state.pageStart, page - 1); + this.state.pageNext = Math.min(this.state.pageEnd, page + 1); + } + + onWillUpdateProps(nextProps) { + this.computePagerState(nextProps); + } + + async onPageChanged(page) { + this.state.disabledButtons = true; + await this.props.changePage(page); + this.state.disabledButtons = false; + } +} + +ChatterPager.props = { + pagerScope: Number, + pagerStep: Number, + page: Number, + messageCount: Number, + changePage: Function, +}; + +ChatterPager.template = 'project.ChatterPager'; diff --git a/addons/project/static/src/project_sharing/components/chatter/chatter_pager.xml b/addons/project/static/src/project_sharing/components/chatter/chatter_pager.xml new file mode 100644 index 00000000000..85b478d61de --- /dev/null +++ b/addons/project/static/src/project_sharing/components/chatter/chatter_pager.xml @@ -0,0 +1,21 @@ + + + +
+
    +
  • + +
  • + +
  • + +
  • +
    +
  • + +
  • +
+
+
+ +
diff --git a/addons/project/static/src/project_sharing/components/composer.js b/addons/project/static/src/project_sharing/components/composer.js deleted file mode 100644 index a7e75aad119..00000000000 --- a/addons/project/static/src/project_sharing/components/composer.js +++ /dev/null @@ -1,17 +0,0 @@ -/** @odoo-module **/ - -import { PortalComposer } from 'portal.composer'; - -export default PortalComposer.extend({ - _prepareAttachmentData() { - const data = this._super.apply(this, arguments); - const newData = {}; - if (this.options.display_composer && typeof this.options.display_composer == 'string') { - // then we should have the access_token of the task - newData.access_token = this.options.display_composer; - } else { - newData.project_sharing_id = this.options.project_sharing_id; - } - return Object.assign(data, newData); - }, -}); diff --git a/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.js b/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.js new file mode 100644 index 00000000000..df29b68ef5e --- /dev/null +++ b/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.js @@ -0,0 +1,33 @@ +/** @odoo-module */ + +import { PortalFileInput } from '../portal_file_input/portal_file_input'; + +const { Component } = owl; + +export class PortalAttachDocument extends Component {} + +PortalAttachDocument.template = 'project.PortalAttachDocument'; +PortalAttachDocument.components = { PortalFileInput }; +PortalAttachDocument.props = { + highlight: { type: Boolean, optional: true }, + onUpload: { type: Function, optional: true }, + beforeOpen: { type: Function, optional: true }, + slots: { + type: Object, + shape: { + default: Object, + }, + }, + resId: { type: Number, optional: true }, + resModel: { type: String, optional: true }, + multiUpload: { type: Boolean, optional: true }, + hidden: { type: Boolean, optional: true }, + acceptedFileExtensions: { type: String, optional: true }, + token: { type: String, optional: true }, +}; +PortalAttachDocument.defaultProps = { + acceptedFileExtensions: "*", + onUpload: () => {}, + route: "/portal/attachment/add", + beforeOpen: async () => true, +}; diff --git a/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.xml b/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.xml new file mode 100644 index 00000000000..c62cbb88236 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/portal_attach_document/portal_attach_document.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/addons/project/static/src/project_sharing/components/portal_file_input/portal_file_input.js b/addons/project/static/src/project_sharing/components/portal_file_input/portal_file_input.js new file mode 100644 index 00000000000..873f2fa1540 --- /dev/null +++ b/addons/project/static/src/project_sharing/components/portal_file_input/portal_file_input.js @@ -0,0 +1,46 @@ +/** @odoo-module */ + +import { FileInput } from '@web/core/file_input/file_input'; + +export class PortalFileInput extends FileInput { + /** + * @override + */ + get httpParams() { + const { + model: res_model, + id: res_id, + ...otherParams + } = super.httpParams; + return { + res_model, + res_id, + access_token: this.props.accessToken, + ...otherParams, + } + } + + async uploadFiles(params) { + const { ufile: files, ...otherParams } = params; + const filesData = await Promise.all( + files.map( + (file) => + super.uploadFiles({ + file, + name: file.name, + ...otherParams, + }) + ) + ); + return filesData; + } +} + +PortalFileInput.props = { + ...FileInput.props, + accessToken: { type: String, optional: true }, +}; +PortalFileInput.defaultProps = { + ...FileInput.defaultProps, + accessToken: '', +}; diff --git a/addons/project/static/src/project_sharing/views/form/controller.js b/addons/project/static/src/project_sharing/views/form/controller.js deleted file mode 100644 index b54daf2167e..00000000000 --- a/addons/project/static/src/project_sharing/views/form/controller.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @odoo-module **/ - -import FormController from 'web.FormController'; -import BasicController from 'web.BasicController'; - -export default FormController.extend({ - _getActionMenuItems: BasicController.prototype._getActionMenuItems, -}); diff --git a/addons/project/static/src/project_sharing/views/form/project_sharing_form_compiler.js b/addons/project/static/src/project_sharing/views/form/project_sharing_form_compiler.js new file mode 100644 index 00000000000..8d9109022ce --- /dev/null +++ b/addons/project/static/src/project_sharing/views/form/project_sharing_form_compiler.js @@ -0,0 +1,105 @@ +/** @odoo-module */ + +import { append, createElement, setAttributes } from "@web/core/utils/xml"; +import { registry } from "@web/core/registry"; +import { SIZES } from "@web/core/ui/ui_service"; +import { getModifier, ViewCompiler } from "@web/views/view_compiler"; +import { patch } from "@web/core/utils/patch"; +import { FormCompiler } from "@web/views/form/form_compiler"; + +/** + * Compiler the portal chatter in project sharing. + * + * @param {HTMLElement} node + * @param {Object} params + * @returns + */ +function compileChatter(node, params) { + const chatterContainerXml = createElement('ChatterContainer'); + const parentURLQuery = new URLSearchParams(window.parent.location.search); + setAttributes(chatterContainerXml, { + token: parentURLQuery.get('access_token') || '', + resModel: params.resModel, + resId: params.resId, + projectSharingId: params.projectSharingId, + }); + const chatterContainerHookXml = createElement('div'); + chatterContainerHookXml.classList.add('o_FormRenderer_chatterContainer'); + append(chatterContainerHookXml, chatterContainerXml); + return chatterContainerHookXml; +} + +export class ProjectSharingChatterCompiler extends ViewCompiler { + setup() { + this.compilers.push({ selector: "t", fn: this.compileT }); + this.compilers.push({ selector: 'div.oe_chatter', fn: this.compileChatter }); + } + + compile(node, params) { + const res = super.compile(node, params).children[0]; + const chatterContainerHookXml = res.querySelector(".o_FormRenderer_chatterContainer"); + if (chatterContainerHookXml) { + setAttributes(chatterContainerHookXml, { + "t-if": `uiService.size >= ${SIZES.XXL}`, + }); + chatterContainerHookXml.classList.add('overflow-x-hidden', 'overflow-y-auto', 'o-aside', 'h-100'); + } + return res; + } + + compileT(node, params) { + const compiledRoot = createElement("t"); + for (const child of node.childNodes) { + const invisible = getModifier(child, "invisible"); + let compiledChild = this.compileNode(child, params, false); + compiledChild = this.applyInvisible(invisible, compiledChild, { + ...params, + recordExpr: "model.root", + }); + append(compiledRoot, compiledChild); + } + return compiledRoot; + } + + compileChatter(node) { + return compileChatter(node, { + resId: 'model.root.resId or undefined', + resModel: 'model.root.resModel', + projectSharingId: 'model.root.context.active_id', + }); + } +} + +registry.category("form_compilers").add("portal_chatter_compiler", { + selector: "div.oe_chatter", + fn: (node) => + compileChatter(node, { + resId: "props.record.resId or undefined", + resModel: "props.record.resModel", + projectSharingId: "props.record.context.active_id", + }), +}); + +patch(FormCompiler.prototype, 'project_sharing_chatter', { + compile(node, params) { + const res = this._super(node, params); + const chatterContainerHookXml = res.querySelector('.o_FormRenderer_chatterContainer'); + if (!chatterContainerHookXml) { + return res; // no chatter, keep the result as it is + } + if (chatterContainerHookXml.parentNode.classList.contains('o_form_sheet')) { + return res; // if chatter is inside sheet, keep it there + } + const formSheetBgXml = res.querySelector('.o_form_sheet_bg'); + const parentXml = formSheetBgXml && formSheetBgXml.parentNode; + if (!parentXml) { + return res; // miss-config: a sheet-bg is required for the rest + } + // after sheet bg (standard position, below form) + setAttributes(chatterContainerHookXml, { + 't-if': `uiService.size < ${SIZES.XXL}`, + }); + append(parentXml, chatterContainerHookXml); + return res; + } +}); diff --git a/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.js b/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.js new file mode 100644 index 00000000000..652ab0b5b32 --- /dev/null +++ b/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.js @@ -0,0 +1,31 @@ +/** @odoo-module */ + +import { useService } from '@web/core/utils/hooks'; +import { FormController } from '@web/views/form/form_controller'; +import { useViewCompiler } from '@web/views/view_compiler'; +import { ProjectSharingChatterCompiler } from './project_sharing_form_compiler'; +import { ChatterContainer } from '../../components/chatter/chatter_container'; + +export class ProjectSharingFormController extends FormController { + setup() { + super.setup(); + this.uiService = useService('ui'); + const { arch, xmlDoc } = this.archInfo; + const template = document.createElement('t'); + const xmlDocChatter = xmlDoc.querySelector("div.oe_chatter"); + if (xmlDocChatter && xmlDocChatter.parentNode.nodeName === "form") { + template.appendChild(xmlDocChatter.cloneNode(true)); + } + const mailTemplates = useViewCompiler(ProjectSharingChatterCompiler, arch, { Mail: template }, {}); + this.mailTemplate = mailTemplates.Mail; + } + + getActionMenuItems() { + return {}; + } +} + +ProjectSharingFormController.components = { + ...FormController.components, + ChatterContainer, +} diff --git a/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.xml b/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.xml new file mode 100644 index 00000000000..6db6caaad52 --- /dev/null +++ b/addons/project/static/src/project_sharing/views/form/project_sharing_form_controller.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/addons/project/static/src/project_sharing/views/form/project_sharing_form_renderer.js b/addons/project/static/src/project_sharing/views/form/project_sharing_form_renderer.js new file mode 100644 index 00000000000..1da51d3f415 --- /dev/null +++ b/addons/project/static/src/project_sharing/views/form/project_sharing_form_renderer.js @@ -0,0 +1,10 @@ +/** @odoo-module */ + +import { ChatterContainer } from '../../components/chatter/chatter_container'; +import { FormRenderer } from '@web/views/form/form_renderer'; + +export class ProjectSharingFormRenderer extends FormRenderer { } +ProjectSharingFormRenderer.components = { + ...FormRenderer.components, + ChatterContainer, +}; diff --git a/addons/project/static/src/project_sharing/views/form/project_sharing_form_view.js b/addons/project/static/src/project_sharing/views/form/project_sharing_form_view.js new file mode 100644 index 00000000000..fd916276339 --- /dev/null +++ b/addons/project/static/src/project_sharing/views/form/project_sharing_form_view.js @@ -0,0 +1,8 @@ +/** @odoo-module */ + +import { formView } from '@web/views/form/form_view'; +import { ProjectSharingFormController } from './project_sharing_form_controller'; +import { ProjectSharingFormRenderer } from './project_sharing_form_renderer'; + +formView.Controller = ProjectSharingFormController; +formView.Renderer = ProjectSharingFormRenderer; diff --git a/addons/project/static/src/project_sharing/views/form/renderer.js b/addons/project/static/src/project_sharing/views/form/renderer.js deleted file mode 100644 index a3468ea7736..00000000000 --- a/addons/project/static/src/project_sharing/views/form/renderer.js +++ /dev/null @@ -1,52 +0,0 @@ -/** @odoo-module **/ - -import '@mail/widgets/form_renderer/form_renderer'; // ensure mail overrides are applied first - -import FormRenderer from 'web.FormRenderer'; -import Chatter from '@project/project_sharing/components/chatter'; -import { session } from '@web/session'; - -export default FormRenderer.extend({ - /** - * Creates the portal chatter instead of the standard chatter. - * - * This overridden method is expected to be called from proper lifecycle - * method of mail form renderer, when `this._chatterContainerTarget` is set. - * - * @override - */ - initChatter() { - const options = this.makePortalChatterOptions(); - this.portalChatter = new Chatter(this, options); - this.portalChatter.appendTo(this._chatterContainerTarget); - }, - /** - * Updates the options of the portal chatter instead of the props of the - * standard chatter. - * - * This overridden method is expected to be called from proper lifecycle - * method of mail form renderer. - * - * @override - */ - _updateChatterContainerComponent() { - const options = this.makePortalChatterOptions(); - this.portalChatter.update(options); - }, - makePortalChatterOptions() { - // FIXME: perhaps check if we have a parent in the window ? - // Call the parent window to get the url displayed in the browser since we are in an iframe - const query = new URLSearchParams(window.parent.location.search); - return { - token: query.get('access_token') || '', - res_model: 'project.task', - pid: '', - hash: '', - res_id: this.state.res_id, - pager_step: 10, - allow_composer: !!this.state.res_id, - two_columns: false, - project_sharing_id: session.project_id, - }; - }, -}); diff --git a/addons/project/static/src/project_sharing/views/form/view.js b/addons/project/static/src/project_sharing/views/form/view.js deleted file mode 100644 index 36f7904679b..00000000000 --- a/addons/project/static/src/project_sharing/views/form/view.js +++ /dev/null @@ -1,12 +0,0 @@ -/** @odoo-module **/ - -import FormView from 'web.FormView'; -import Controller from './controller'; -import Renderer from './renderer'; - -export default FormView.extend({ - config: _.extend({}, FormView.prototype.config, { - Controller, - Renderer, - }), -}); diff --git a/addons/project/static/src/project_sharing/views/registry.js b/addons/project/static/src/project_sharing/views/registry.js deleted file mode 100644 index ea68de08d4e..00000000000 --- a/addons/project/static/src/project_sharing/views/registry.js +++ /dev/null @@ -1,15 +0,0 @@ -/** @odoo-module **/ - -import ViewRegistry from 'web.view_registry'; -import { registry } from '@web/core/registry'; -// FIXME: make sure the form view are define before we remove them below -import "@web/views/form/form_view"; - -import FormView from './form/view'; - -// FIXME: remove new views to force the use of the overriden legacy views when -// project sharing form is converted to new views. -registry.category("views").remove("form"); - -ViewRegistry - .add('form', FormView); diff --git a/addons/project/static/src/scss/project_sharing_frontend.scss b/addons/project/static/src/scss/project_sharing_frontend.scss new file mode 100644 index 00000000000..6c18b30def8 --- /dev/null +++ b/addons/project/static/src/scss/project_sharing_frontend.scss @@ -0,0 +1,3 @@ +.o_project_sharing_container > main { + display: flex; +} diff --git a/addons/project/static/tests/tours/project_sharing_tour.js b/addons/project/static/tests/tours/project_sharing_tour.js index c66fb7b2e2f..0a0a83d1c0d 100644 --- a/addons/project/static/tests/tours/project_sharing_tour.js +++ b/addons/project/static/tests/tours/project_sharing_tour.js @@ -49,14 +49,14 @@ const projectSharingSteps = [...tour.stepUtils.goToAppSteps("project.menu_main_p trigger: '.o_kanban_quick_create .o_kanban_edit', content: 'Go to the form view of this new task', }, { - trigger: 'div.o_statusbar_status[name="stage_id"] button[aria-checked="false"]:contains(Done)', + trigger: 'div[name="stage_id"] div.o_statusbar_status button[aria-checked="false"]:contains(Done)', content: 'Change the stage of the task.', }, { - trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body textarea[name="message"]', + trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body textarea', content: 'Write a message in the chatter of the task', run: 'text I create a new task for testing purpose.', }, { - trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body button[data-action="/mail/chatter_post"]', + trigger: '.o_portal_chatter_composer_input .o_portal_chatter_composer_body button[name="send_message"]', content: 'Send the message', }, { trigger: 'ol.breadcrumb > li.o_back_button > a:contains(Project Sharing)', diff --git a/addons/project/tests/test_project_sharing_ui.py b/addons/project/tests/test_project_sharing_ui.py index 16f532c4066..70de1324f99 100644 --- a/addons/project/tests/test_project_sharing_ui.py +++ b/addons/project/tests/test_project_sharing_ui.py @@ -39,7 +39,7 @@ class TestProjectSharingUi(HttpCase): def test_01_project_sharing(self): """ Test Project Sharing UI with an internal user """ - self.start_tour("/web?debug=assets", 'project_sharing_tour', login="admin") + self.start_tour("/web", 'project_sharing_tour', login="admin") def test_02_project_sharing(self): """ Test project sharing ui with a portal user. diff --git a/addons/project/views/project_sharing_templates.xml b/addons/project/views/project_sharing_templates.xml index 5182521f05d..b00bb5512ba 100644 --- a/addons/project/views/project_sharing_templates.xml +++ b/addons/project/views/project_sharing_templates.xml @@ -3,6 +3,8 @@