[IMP] mail: improve attachment_viewer

This commit improves the expendability of the attachment_viewer model
and component by making it less dependent on attachment_list.

This is required by the refactoring in wowl of the documents app(s).

TaskId-2898342

closes odoo/odoo#94930

Related: odoo/enterprise#28976
Signed-off-by: Yannick Tivisse (yti) <yti@odoo.com>
This commit is contained in:
William Braeckman
2022-07-15 23:29:44 +02:00
parent 297d8675e2
commit f3590959b0
5 changed files with 186 additions and 45 deletions
@@ -93,13 +93,13 @@ export class AttachmentViewer extends Component {
* @private
*/
_handleImageLoad() {
if (!this.attachmentViewer.exists() || !this.attachmentViewer.attachment) {
if (!this.attachmentViewer.exists() || !this.attachmentViewer.attachmentViewerViewable) {
return;
}
const refs = this._getRefs();
const image = refs[`image_${this.attachmentViewer.attachment.id}`];
const image = refs[`image_${this.attachmentViewer.attachmentViewerViewable.localId}`];
if (
this.attachmentViewer.attachment.isImage &&
this.attachmentViewer.attachmentViewerViewable.isImage &&
(!image || !image.complete)
) {
this.attachmentViewer.update({ isImageLoading: true });
@@ -142,7 +142,7 @@ export class AttachmentViewer extends Component {
_updateZoomerStyle() {
const attachmentViewer = this.attachmentViewer;
const refs = this._getRefs();
const image = refs[`image_${this.attachmentViewer.attachment.id}`];
const image = refs[`image_${this.attachmentViewer.attachmentViewerViewable.localId}`];
const tx = image.offsetWidth * attachmentViewer.scale > this._zoomerRef.el.offsetWidth
? this._translate.x + this._translate.dx
: 0;
@@ -5,24 +5,24 @@
<t t-if="attachmentViewer">
<div class="o_AttachmentViewer flex-column align-items-center d-flex w-100 h-100" t-attf-class="{{ className }}" t-on-click="attachmentViewer.onClick" t-on-keydown="_onKeydown" tabindex="0" t-ref="root">
<div class="o_AttachmentViewer_header d-flex w-100 bg-black-75 text-400" t-on-click="attachmentViewer.onClickHeader">
<t t-if="attachmentViewer.attachment.isViewable">
<t t-if="attachmentViewer.attachmentViewerViewable.isViewable">
<div class="o_AttachmentViewer_headerItem o_AttachmentViewer_icon d-flex align-items-center ms-4 me-2">
<t t-if="attachmentViewer.attachment.isImage">
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
<i class="fa fa-picture-o" role="img" title="Image"/>
</t>
<t t-if="attachmentViewer.attachment.isPdf">
<t t-if="attachmentViewer.attachmentViewerViewable.isPdf">
<i class="fa fa-file-text" role="img" title="PDF file"/>
</t>
<t t-if="attachmentViewer.attachment.isText">
<t t-if="attachmentViewer.attachmentViewerViewable.isText">
<i class="fa fa-file-text" role="img" title="Text file"/>
</t>
<t t-if="attachmentViewer.attachment.isVideo">
<t t-if="attachmentViewer.attachmentViewerViewable.isVideo">
<i class="fa fa-video-camera" role="img" title="Video"/>
</t>
</div>
</t>
<div class="o_AttachmentViewer_headerItem o_AttachmentViewer_name d-flex align-items-center mx-2">
<span class="o_AttachmentViewer_nameText text-truncate" t-esc="attachmentViewer.attachment.displayName"/>
<span class="o_AttachmentViewer_nameText text-truncate" t-esc="attachmentViewer.attachmentViewerViewable.displayName"/>
</div>
<div class="flex-grow-1"/>
<div class="o_AttachmentViewer_buttonDownload o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton d-flex align-items-center px-3 o_cursor_pointer" t-on-click="attachmentViewer.onClickDownload" role="button" title="Download">
@@ -35,33 +35,33 @@
<i class="fa fa-fw fa-times" role="img"/>
</div>
</div>
<div class="o_AttachmentViewer_main position-absolute top-0 bottom-0 start-0 end-0 align-items-center justify-content-center d-flex" t-att-class="{ 'o_with_img overflow-hidden': attachmentViewer.attachment.isImage }" t-on-mousemove="_onMousemoveView">
<t t-if="attachmentViewer.attachment.isImage">
<div class="o_AttachmentViewer_main position-absolute top-0 bottom-0 start-0 end-0 align-items-center justify-content-center d-flex" t-att-class="{ 'o_with_img overflow-hidden': attachmentViewer.attachmentViewerViewable.isImage }" t-on-mousemove="_onMousemoveView">
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
<div class="o_AttachmentViewer_zoomer position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="zoomer">
<t t-if="attachmentViewer.isImageLoading">
<div class="o_AttachmentViewer_loading position-absolute">
<i class="fa fa-3x fa-circle-o-notch fa-fw fa-spin text-white" role="img" title="Loading"/>
</div>
</t>
<img class="o_AttachmentViewer_view o_AttachmentViewer_viewImage mw-100 mh-100 bg-black" t-on-click="attachmentViewer.onClickImage" t-on-mousedown="_onMousedownImage" t-on-wheel="_onWheelImage" t-on-load="attachmentViewer.onLoadImage" t-att-src="attachmentViewer.imageUrl" t-att-style="attachmentViewer.imageStyle" draggable="false" alt="Viewer" t-key="'image_' + attachmentViewer.attachment.id" t-ref="image_{{ attachmentViewer.attachment.id }}"/>
<img class="o_AttachmentViewer_view o_AttachmentViewer_viewImage mw-100 mh-100 bg-black" t-on-click="attachmentViewer.onClickImage" t-on-mousedown="_onMousedownImage" t-on-wheel="_onWheelImage" t-on-load="attachmentViewer.onLoadImage" t-att-src="attachmentViewer.attachmentViewerViewable.imageUrl" t-att-style="attachmentViewer.imageStyle" draggable="false" alt="Viewer" t-key="'image_' + attachmentViewer.attachmentViewerViewable.localId" t-ref="image_{{ attachmentViewer.attachmentViewerViewable.localId }}"/>
</div>
</t>
<t t-if="attachmentViewer.attachment.isPdf">
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_viewPdf w-75 h-100 bg-black border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': messaging.device.isSmall }" t-att-src="attachmentViewer.attachment.defaultSource"/>
<t t-if="attachmentViewer.attachmentViewerViewable.isPdf">
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_viewPdf w-75 h-100 bg-black border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': messaging.device.isSmall }" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
</t>
<t t-if="attachmentViewer.attachment.isText">
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_text w-75 h-100 bg-white border-0" t-att-src="attachmentViewer.attachment.defaultSource"/>
<t t-if="attachmentViewer.attachmentViewerViewable.isText">
<iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_text w-75 h-100 bg-white border-0" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
</t>
<t t-if="attachmentViewer.attachment.isUrlYoutube">
<iframe allow="autoplay; encrypted-media" class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_youtube w-75 h-100 bg-black border-0" t-att-src="attachmentViewer.attachment.defaultSource" height="315" width="560"/>
<t t-if="attachmentViewer.attachmentViewerViewable.isUrlYoutube">
<iframe allow="autoplay; encrypted-media" class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_youtube w-75 h-100 bg-black border-0" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource" height="315" width="560"/>
</t>
<t t-if="attachmentViewer.attachment.isVideo">
<t t-if="attachmentViewer.attachmentViewerViewable.isVideo">
<video class="o_AttachmentViewer_view o_AttachmentViewer_viewVideo w-75 h-75 bg-black" t-att-class="{ 'w-100 h-100': messaging.device.isSmall }" t-on-click="attachmentViewer.onClickVideo" controls="controls">
<source t-att-data-type="attachmentViewer.attachment.mimetype" t-att-src="attachmentViewer.attachment.defaultSource"/>
<source t-att-data-type="attachmentViewer.attachmentViewerViewable.mimetype" t-att-src="attachmentViewer.attachmentViewerViewable.defaultSource"/>
</video>
</t>
</div>
<t t-if="attachmentViewer.attachment.isImage">
<t t-if="attachmentViewer.attachmentViewerViewable.isImage">
<div class="o_AttachmentViewer_toolbar position-absolute bottom-0 d-flex" role="toolbar">
<div class="o_AttachmentViewer_toolbarButton btn btn-dark p-3 rounded-0" t-on-click="_onClickZoomIn" title="Zoom In (+)" role="button">
<i class="fa fa-fw fa-plus" role="img"/>
@@ -83,7 +83,7 @@
</div>
</div>
</t>
<t t-if="attachmentViewer.attachments.length > 1">
<t t-if="attachmentViewer.attachmentViewerViewables.length > 1">
<div class="o_AttachmentViewer_buttonNavigation o_AttachmentViewer_buttonNavigationPrevious o_AttachmentViewer_buttonNavigationPreviousIcon btn btn-dark position-absolute top-0 bottom-0 start-0 align-items-center justify-content-center d-flex my-auto ms-3 rounded-circle" t-on-click="attachmentViewer.onClickPrevious" title="Previous (Left-Arrow)" role="button">
<span class="fa fa-chevron-left" role="img"/>
</div>
@@ -300,6 +300,10 @@ registerModel({
attachmentLists: many('AttachmentList', {
inverse: 'attachments',
}),
attachmentViewerViewable: one('AttachmentViewerViewable', {
inverse: 'attachmentOwner',
isCausal: true,
}),
checksum: attr(),
/**
* States on which composer this attachment is currently being created.
@@ -2,6 +2,7 @@
import { registerModel } from '@mail/model/model_core';
import { attr, many, one } from '@mail/model/model_field';
import { clear, insertAndReplace, replace } from '@mail/model/model_field_command';
registerModel({
name: 'AttachmentViewer',
@@ -59,7 +60,7 @@ registerModel({
*/
onClickDownload(ev) {
ev.stopPropagation();
this.attachment.download();
this.attachmentViewerViewable.download();
},
/**
* Called when clicking on the header. Stop propagation of event to prevent
@@ -181,17 +182,27 @@ registerModel({
},
/**
* @private
* @returns {string}
*/
_computeImageUrl() {
if (!this.attachment) {
return;
_computeAttachmentViewerViewable() {
if (this.attachmentList) {
return insertAndReplace({
attachmentOwner: replace(this.attachmentList.selectedAttachment),
});
}
if (!this.attachment.accessToken && this.attachment.originThread && this.attachment.originThread.model === 'mail.channel') {
return `/mail/channel/${this.attachment.originThread.id}/image/${this.attachment.id}`;
return clear();
},
/**
* @private
*/
_computeAttachmentViewerViewables() {
if (this.attachmentList) {
return insertAndReplace(
this.attachmentList.viewableAttachments.map((attachment) => {
return { attachmentOwner: replace(attachment) };
})
);
}
const accessToken = this.attachment.accessToken ? `?access_token=${this.attachment.accessToken}` : '';
return `/web/image/${this.attachment.id}${accessToken}`;
return clear();
},
/**
* @private
@@ -221,15 +232,14 @@ registerModel({
angle: attr({
default: 0,
}),
attachment: one('Attachment', {
related: 'attachmentList.selectedAttachment',
}),
attachmentList: one('AttachmentList', {
related: 'dialogOwner.attachmentListOwnerAsAttachmentView',
required: true,
}),
attachments: many('Attachment', {
related: 'attachmentList.viewableAttachments',
attachmentViewerViewable: one("AttachmentViewerViewable", {
compute: "_computeAttachmentViewerViewable",
}),
attachmentViewerViewables: many("AttachmentViewerViewable", {
compute: "_computeAttachmentViewerViewables",
}),
/**
* States the OWL component of this attachment viewer.
@@ -249,13 +259,6 @@ registerModel({
imageStyle: attr({
compute: '_computeImageStyle',
}),
/**
* Determines the source URL to use for the image.
*/
imageUrl: attr({
compute: '_computeImageUrl',
readonly: true,
}),
/**
* Determine whether the user is currently dragging the image.
* This is useful to determine whether a click outside of the image
@@ -0,0 +1,134 @@
/** @odoo-module **/
import { registerModel } from "@mail/model/model_core";
import { attr, one } from "@mail/model/model_field";
/**
* Intermediary model to facilitate adding support for additional
* models to the AttachmentViewer.
*/
registerModel({
name: "AttachmentViewerViewable",
identifyingFields: [["attachmentOwner"]],
recordMethods: {
download() {
return this.attachmentOwner.download();
},
/**
* @private
*/
_computeAccessToken() {
return this.attachmentOwner.accessToken;
},
/**
* @private
*/
_computeDefaultSource() {
return this.attachmentOwner.defaultSource;
},
/**
* @private
*/
_computeDisplayName() {
return this.attachmentOwner.displayName;
},
/**
* @private
*/
_computeImageUrl() {
if (
!this.attachmentOwner.accessToken &&
this.attachmentOwner.originThread &&
this.attachmentOwner.originThread.model === "mail.channel"
) {
return `/mail/channel/${this.attachmentOwner.originThread.id}/image/${this.attachmentOwner.id}`;
}
const accessToken = this.attachmentOwner.accessToken
? `?access_token=${this.attachmentOwner.accessToken}`
: "";
return `/web/image/${this.attachmentOwner.id}${accessToken}`;
},
/**
* @private
*/
_computeIsImage() {
return this.attachmentOwner.isImage;
},
/**
* @private
*/
_computeIsPdf() {
return this.attachmentOwner.isPdf;
},
/**
* @private
*/
_computeIsText() {
return this.attachmentOwner.isText;
},
/**
* @private
*/
_computeIsUrlYoutube() {
return this.attachmentOwner.isUrlYoutube;
},
/**
* @private
*/
_computeIsVideo() {
return this.attachmentOwner.isVideo;
},
/**
* @private
*/
_computeIsViewable() {
return this.attachmentOwner.isViewable;
},
/**
* @private
*/
_computeMimetype() {
return this.attachmentOwner.mimetype;
},
},
fields: {
attachmentOwner: one("Attachment", {
inverse: 'attachmentViewerViewable',
readonly: true,
}),
accessToken: attr({
compute: "_computeAccessToken",
}),
defaultSource: attr({
compute: "_computeDefaultSource",
}),
displayName: attr({
compute: "_computeDisplayName",
}),
imageUrl: attr({
compute: "_computeImageUrl",
}),
isImage: attr({
compute: "_computeIsImage",
}),
isPdf: attr({
compute: "_computeIsPdf",
}),
isText: attr({
compute: "_computeIsText",
}),
isUrlYoutube: attr({
compute: "_computeIsUrlYoutube",
}),
isVideo: attr({
compute: "_computeIsVideo",
}),
isViewable: attr({
compute: "_computeIsViewable",
}),
mimetype: attr({
compute: "_computeMimetype",
}),
},
});