[IMP] mail: add hover download button on image attachments

PURPOSE

Improve how image attachments are downloaded from the chatter by adding the
`download` button when hovering image files

SPECIFICATIONS

add a `Download` button (icon) on the bottom right corner of image attachments
on hover

Task-2802810

closes odoo/odoo#92013

Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
Rahul Prajapati
2022-06-01 11:02:26 +02:00
committed by Alexandre Kühn
parent 947770ce7e
commit 222ae02cea
3 changed files with 79 additions and 12 deletions
@@ -20,15 +20,14 @@
<i class="fa fa-spin fa-spinner"/>
</div>
</t>
<t t-if="attachmentImage.attachment.isDeletable">
<div class="o_AttachmentImage_imageOverlay position-absolute top-0 bottom-0 start-0 end-0 text-right p-2 text-white opacity-0 opacity-100-hover">
<div class="o_AttachmentImage_action o_AttachmentImage_actionUnlink btn btn-sm btn-dark rounded opacity-75 opacity-100-hover"
t-att-class="{'o-pretty': attachmentImage.attachmentList.composerViewOwner}" t-on-click="attachmentImage.onClickUnlink" title="Remove"
>
<i class="fa fa-trash"/>
</div>
<div class="o_AttachmentImage_imageOverlay position-absolute top-0 bottom-0 start-0 end-0 p-2 text-white opacity-0 opacity-100-hover d-flex align-items-end flax-wrap flex-column">
<div t-if="attachmentImage.attachment.isDeletable" class="o_AttachmentImage_action o_AttachmentImage_actionUnlink btn btn-sm btn-dark rounded opacity-75 opacity-100-hover" t-att-class="{'o-pretty': attachmentImage.attachmentList.composerViewOwner}" t-on-click="attachmentImage.onClickUnlink" title="Remove">
<i class="fa fa-trash"/>
</div>
</t>
<div t-if="attachmentImage.hasDownloadButton" class="o_AttachmentImage_action o_AttachmentImage_actionDownload btn btn-sm btn-dark rounded opacity-75 opacity-100-hover mt-auto" t-on-click="attachmentImage.onClickDownload" title="Download">
<i class="fa fa-download"/>
</div>
</div>
</div>
</div>
</t>
@@ -3,15 +3,36 @@
import { registerModel } from '@mail/model/model_core';
import { attr, one } from '@mail/model/model_field';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
import { isEventHandled, markEventHandled } from '@mail/utils/utils';
registerModel({
name: 'AttachmentImage',
identifyingFields: ['attachmentList', 'attachment'],
recordMethods: {
/**
* Opens the attachment viewer when clicking on viewable attachment.
* Called when clicking on download icon.
*
* @param {MouseEvent} ev
*/
onClickImage() {
onClickDownload(ev) {
markEventHandled(ev, 'AttachmentImage.onClickDownload');
if (!this.exists()) {
return;
}
this.attachment.download();
},
/**
* Opens the attachment viewer when clicking on viewable attachment.
*
* @param {MouseEvent} ev
*/
onClickImage(ev) {
if (isEventHandled(ev, 'AttachmentImage.onClickDownload')) {
return;
}
if (isEventHandled(ev, 'AttachmentImage.onClickUnlink')) {
return;
}
if (!this.attachment || !this.attachment.isViewable) {
return;
}
@@ -26,8 +47,8 @@ registerModel({
* @param {MouseEvent} ev
*/
onClickUnlink(ev) {
ev.stopPropagation(); // prevents from opening viewer
if (!this.attachment) {
markEventHandled(ev, 'AttachmentImage.onClickUnlink');
if (!this.exists()) {
return;
}
if (this.attachmentList.composerViewOwner) {
@@ -36,6 +57,16 @@ registerModel({
this.update({ attachmentDeleteConfirmDialog: insertAndReplace() });
}
},
/**
* @private
* @returns {boolean}
*/
_computeHasDownloadButton() {
if (!this.attachment || !this.attachmentList) {
return clear();
}
return !this.attachmentList.composerViewOwner && !this.attachment.isUploading;
},
/**
* @private
* @returns {number}
@@ -99,6 +130,13 @@ registerModel({
readonly: true,
required: true,
}),
/**
* Determines whether `this` should display a download button.
*/
hasDownloadButton: attr({
compute: '_computeHasDownloadButton',
default: false,
}),
/**
* Determines the max height of this attachment image in px.
*/
@@ -698,6 +698,36 @@ QUnit.test('prevent attachment delete on non-authored message in channels', asyn
);
});
QUnit.test('allow attachment image download on message', async function (assert) {
assert.expect(1);
const pyEnv = await startServer();
const mailChannelId1 = pyEnv['mail.channel'].create({});
const irAttachmentId1 = pyEnv['ir.attachment'].create({
name: "Blah.jpg",
mimetype: 'image/jpeg',
});
pyEnv['mail.message'].create({
attachment_ids: [irAttachmentId1],
body: '<p>Test</p>',
model: 'mail.channel',
res_id: mailChannelId1,
});
const { openDiscuss } = await start({
discuss: {
context: {
active_id: mailChannelId1,
},
},
});
await openDiscuss();
assert.containsOnce(
document.body,
'.o_AttachmentImage_actionDownload',
"should have download attachment button"
);
});
QUnit.test('subtype description should be displayed if it is different than body', async function (assert) {
assert.expect(2);