summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components/attachment_viewer
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/components/attachment_viewer
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/attachment_viewer')
-rw-r--r--addons/mail/static/src/components/attachment_viewer/attachment_viewer.js598
-rw-r--r--addons/mail/static/src/components/attachment_viewer/attachment_viewer.scss198
-rw-r--r--addons/mail/static/src/components/attachment_viewer/attachment_viewer.xml93
3 files changed, 889 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js
new file mode 100644
index 00000000..30755fd9
--- /dev/null
+++ b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.js
@@ -0,0 +1,598 @@
+odoo.define('mail/static/src/components/attachment_viewer/attachment_viewer.js', function (require) {
+'use strict';
+
+const useRefs = require('mail/static/src/component_hooks/use_refs/use_refs.js');
+const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js');
+const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
+
+const { Component, QWeb } = owl;
+const { useRef } = owl.hooks;
+
+const MIN_SCALE = 0.5;
+const SCROLL_ZOOM_STEP = 0.1;
+const ZOOM_STEP = 0.5;
+
+class AttachmentViewer extends Component {
+
+ /**
+ * @override
+ */
+ constructor(...args) {
+ super(...args);
+ this.MIN_SCALE = MIN_SCALE;
+ useShouldUpdateBasedOnProps();
+ useStore(props => {
+ const attachmentViewer = this.env.models['mail.attachment_viewer'].get(props.localId);
+ return {
+ attachment: attachmentViewer && attachmentViewer.attachment
+ ? attachmentViewer.attachment.__state
+ : undefined,
+ attachments: attachmentViewer
+ ? attachmentViewer.attachments.map(attachment => attachment.__state)
+ : [],
+ attachmentViewer: attachmentViewer ? attachmentViewer.__state : undefined,
+ };
+ });
+ /**
+ * Used to ensure that the ref is always up to date, which seems to be needed if the element
+ * has a t-key, which was added to force the rendering of a new element when the src of the image changes.
+ * This was made to remove the display of the previous image as soon as the src changes.
+ */
+ this._getRefs = useRefs();
+ /**
+ * Determine whether the user is currently dragging the image.
+ * This is useful to determine whether a click outside of the image
+ * should close the attachment viewer or not.
+ */
+ this._isDragging = false;
+ /**
+ * Reference of the zoomer node. Useful to apply translate
+ * transformation on image visualisation.
+ */
+ this._zoomerRef = useRef('zoomer');
+ /**
+ * Tracked translate transformations on image visualisation. This is
+ * not observed with `useStore` because they are used to compute zoomer
+ * style, and this is changed directly on zoomer for performance
+ * reasons (overhead of making vdom is too significant for each mouse
+ * position changes while dragging)
+ */
+ this._translate = { x: 0, y: 0, dx: 0, dy: 0 };
+ this._onClickGlobal = this._onClickGlobal.bind(this);
+ }
+
+ mounted() {
+ this.el.focus();
+ this._handleImageLoad();
+ document.addEventListener('click', this._onClickGlobal);
+ }
+
+ /**
+ * When a new image is displayed, show a spinner until it is loaded.
+ */
+ patched() {
+ this._handleImageLoad();
+ }
+
+ willUnmount() {
+ document.removeEventListener('click', this._onClickGlobal);
+ }
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @returns {mail.attachment_viewer}
+ */
+ get attachmentViewer() {
+ return this.env.models['mail.attachment_viewer'].get(this.props.localId);
+ }
+
+ /**
+ * Compute the style of the image (scale + rotation).
+ *
+ * @returns {string}
+ */
+ get imageStyle() {
+ const attachmentViewer = this.attachmentViewer;
+ let style = `transform: ` +
+ `scale3d(${attachmentViewer.scale}, ${attachmentViewer.scale}, 1) ` +
+ `rotate(${attachmentViewer.angle}deg);`;
+
+ if (attachmentViewer.angle % 180 !== 0) {
+ style += `` +
+ `max-height: ${window.innerWidth}px; ` +
+ `max-width: ${window.innerHeight}px;`;
+ } else {
+ style += `` +
+ `max-height: 100%; ` +
+ `max-width: 100%;`;
+ }
+ return style;
+ }
+
+ /**
+ * Mandatory method for dialog components.
+ * Prevent closing the dialog when clicking on the mask when the user is
+ * currently dragging the image.
+ *
+ * @returns {boolean}
+ */
+ isCloseable() {
+ return !this._isDragging;
+ }
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Close the dialog with this attachment viewer.
+ *
+ * @private
+ */
+ _close() {
+ this.attachmentViewer.close();
+ }
+
+ /**
+ * Download the attachment.
+ *
+ * @private
+ */
+ _download() {
+ const id = this.attachmentViewer.attachment.id;
+ this.env.services.navigate(`/web/content/ir.attachment/${id}/datas`, { download: true });
+ }
+
+ /**
+ * Determine whether the current image is rendered for the 1st time, and if
+ * that's the case, display a spinner until loaded.
+ *
+ * @private
+ */
+ _handleImageLoad() {
+ if (!this.attachmentViewer || !this.attachmentViewer.attachment) {
+ return;
+ }
+ const refs = this._getRefs();
+ const image = refs[`image_${this.attachmentViewer.attachment.id}`];
+ if (
+ this.attachmentViewer.attachment.fileType === 'image' &&
+ (!image || !image.complete)
+ ) {
+ this.attachmentViewer.update({ isImageLoading: true });
+ }
+ }
+
+ /**
+ * Display the previous attachment in the list of attachments.
+ *
+ * @private
+ */
+ _next() {
+ const attachmentViewer = this.attachmentViewer;
+ const index = attachmentViewer.attachments.findIndex(attachment =>
+ attachment === attachmentViewer.attachment
+ );
+ const nextIndex = (index + 1) % attachmentViewer.attachments.length;
+ attachmentViewer.update({
+ attachment: [['link', attachmentViewer.attachments[nextIndex]]],
+ });
+ }
+
+ /**
+ * Display the previous attachment in the list of attachments.
+ *
+ * @private
+ */
+ _previous() {
+ const attachmentViewer = this.attachmentViewer;
+ const index = attachmentViewer.attachments.findIndex(attachment =>
+ attachment === attachmentViewer.attachment
+ );
+ const nextIndex = index === 0
+ ? attachmentViewer.attachments.length - 1
+ : index - 1;
+ attachmentViewer.update({
+ attachment: [['link', attachmentViewer.attachments[nextIndex]]],
+ });
+ }
+
+ /**
+ * Prompt the browser print of this attachment.
+ *
+ * @private
+ */
+ _print() {
+ const printWindow = window.open('about:blank', '_new');
+ printWindow.document.open();
+ printWindow.document.write(`
+ <html>
+ <head>
+ <script>
+ function onloadImage() {
+ setTimeout('printImage()', 10);
+ }
+ function printImage() {
+ window.print();
+ window.close();
+ }
+ </script>
+ </head>
+ <body onload='onloadImage()'>
+ <img src="${this.attachmentViewer.attachment.defaultSource}" alt=""/>
+ </body>
+ </html>`);
+ printWindow.document.close();
+ }
+
+ /**
+ * Rotate the image by 90 degrees to the right.
+ *
+ * @private
+ */
+ _rotate() {
+ this.attachmentViewer.update({ angle: this.attachmentViewer.angle + 90 });
+ }
+
+ /**
+ * Stop dragging interaction of the user.
+ *
+ * @private
+ */
+ _stopDragging() {
+ this._isDragging = false;
+ this._translate.x += this._translate.dx;
+ this._translate.y += this._translate.dy;
+ this._translate.dx = 0;
+ this._translate.dy = 0;
+ this._updateZoomerStyle();
+ }
+
+ /**
+ * Update the style of the zoomer based on translate transformation. Changes
+ * are directly applied on zoomer, instead of triggering re-render and
+ * defining them in the template, for performance reasons.
+ *
+ * @private
+ * @returns {string}
+ */
+ _updateZoomerStyle() {
+ const attachmentViewer = this.attachmentViewer;
+ const refs = this._getRefs();
+ const image = refs[`image_${this.attachmentViewer.attachment.id}`];
+ const tx = image.offsetWidth * attachmentViewer.scale > this._zoomerRef.el.offsetWidth
+ ? this._translate.x + this._translate.dx
+ : 0;
+ const ty = image.offsetHeight * attachmentViewer.scale > this._zoomerRef.el.offsetHeight
+ ? this._translate.y + this._translate.dy
+ : 0;
+ if (tx === 0) {
+ this._translate.x = 0;
+ }
+ if (ty === 0) {
+ this._translate.y = 0;
+ }
+ this._zoomerRef.el.style = `transform: ` +
+ `translate(${tx}px, ${ty}px)`;
+ }
+
+ /**
+ * Zoom in the image.
+ *
+ * @private
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.scroll=false]
+ */
+ _zoomIn({ scroll = false } = {}) {
+ this.attachmentViewer.update({
+ scale: this.attachmentViewer.scale + (scroll ? SCROLL_ZOOM_STEP : ZOOM_STEP),
+ });
+ this._updateZoomerStyle();
+ }
+
+ /**
+ * Zoom out the image.
+ *
+ * @private
+ * @param {Object} [param0={}]
+ * @param {boolean} [param0.scroll=false]
+ */
+ _zoomOut({ scroll = false } = {}) {
+ if (this.attachmentViewer.scale === MIN_SCALE) {
+ return;
+ }
+ const unflooredAdaptedScale = (
+ this.attachmentViewer.scale -
+ (scroll ? SCROLL_ZOOM_STEP : ZOOM_STEP)
+ );
+ this.attachmentViewer.update({
+ scale: Math.max(MIN_SCALE, unflooredAdaptedScale),
+ });
+ this._updateZoomerStyle();
+ }
+
+ /**
+ * Reset the zoom scale of the image.
+ *
+ * @private
+ */
+ _zoomReset() {
+ this.attachmentViewer.update({ scale: 1 });
+ this._updateZoomerStyle();
+ }
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when clicking on mask of attachment viewer.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClick(ev) {
+ if (this._isDragging) {
+ return;
+ }
+ // TODO: clicking on the background should probably be handled by the dialog?
+ // task-2092965
+ this._close();
+ }
+
+ /**
+ * Called when clicking on cross icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickClose(ev) {
+ this._close();
+ }
+
+ /**
+ * Called when clicking on download icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickDownload(ev) {
+ ev.stopPropagation();
+ this._download();
+ }
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickGlobal(ev) {
+ if (!this._isDragging) {
+ return;
+ }
+ ev.stopPropagation();
+ this._stopDragging();
+ }
+
+ /**
+ * Called when clicking on the header. Stop propagation of event to prevent
+ * closing the dialog.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickHeader(ev) {
+ ev.stopPropagation();
+ }
+
+ /**
+ * Called when clicking on image. Stop propagation of event to prevent
+ * closing the dialog.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickImage(ev) {
+ if (this._isDragging) {
+ return;
+ }
+ ev.stopPropagation();
+ }
+
+ /**
+ * Called when clicking on next icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickNext(ev) {
+ ev.stopPropagation();
+ this._next();
+ }
+
+ /**
+ * Called when clicking on previous icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPrevious(ev) {
+ ev.stopPropagation();
+ this._previous();
+ }
+
+ /**
+ * Called when clicking on print icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickPrint(ev) {
+ ev.stopPropagation();
+ this._print();
+ }
+
+ /**
+ * Called when clicking on rotate icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickRotate(ev) {
+ ev.stopPropagation();
+ this._rotate();
+ }
+
+ /**
+ * Called when clicking on embed video player. Stop propagation to prevent
+ * closing the dialog.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickVideo(ev) {
+ ev.stopPropagation();
+ }
+
+ /**
+ * Called when clicking on zoom in icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickZoomIn(ev) {
+ ev.stopPropagation();
+ this._zoomIn();
+ }
+
+ /**
+ * Called when clicking on zoom out icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickZoomOut(ev) {
+ ev.stopPropagation();
+ this._zoomOut();
+ }
+
+ /**
+ * Called when clicking on reset zoom icon.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickZoomReset(ev) {
+ ev.stopPropagation();
+ this._zoomReset();
+ }
+
+ /**
+ * @private
+ * @param {KeyboardEvent} ev
+ */
+ _onKeydown(ev) {
+ switch (ev.key) {
+ case 'ArrowRight':
+ this._next();
+ break;
+ case 'ArrowLeft':
+ this._previous();
+ break;
+ case 'Escape':
+ this._close();
+ break;
+ case 'q':
+ this._close();
+ break;
+ case 'r':
+ this._rotate();
+ break;
+ case '+':
+ this._zoomIn();
+ break;
+ case '-':
+ this._zoomOut();
+ break;
+ case '0':
+ this._zoomReset();
+ break;
+ default:
+ return;
+ }
+ ev.stopPropagation();
+ }
+
+ /**
+ * Called when new image has been loaded
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onLoadImage(ev) {
+ ev.stopPropagation();
+ this.attachmentViewer.update({ isImageLoading: false });
+ }
+
+ /**
+ * @private
+ * @param {DragEvent} ev
+ */
+ _onMousedownImage(ev) {
+ if (this._isDragging) {
+ return;
+ }
+ if (ev.button !== 0) {
+ return;
+ }
+ ev.stopPropagation();
+ this._isDragging = true;
+ this._dragstartX = ev.clientX;
+ this._dragstartY = ev.clientY;
+ }
+
+ /**
+ * @private
+ * @param {DragEvent}
+ */
+ _onMousemoveView(ev) {
+ if (!this._isDragging) {
+ return;
+ }
+ this._translate.dx = ev.clientX - this._dragstartX;
+ this._translate.dy = ev.clientY - this._dragstartY;
+ this._updateZoomerStyle();
+ }
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onWheelImage(ev) {
+ ev.stopPropagation();
+ if (!this.el) {
+ return;
+ }
+ if (ev.deltaY > 0) {
+ this._zoomOut({ scroll: true });
+ } else {
+ this._zoomIn({ scroll: true });
+ }
+ }
+
+}
+
+Object.assign(AttachmentViewer, {
+ props: {
+ localId: String,
+ },
+ template: 'mail.AttachmentViewer',
+});
+
+QWeb.registerComponent('AttachmentViewer', AttachmentViewer);
+
+return AttachmentViewer;
+
+});
diff --git a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.scss b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.scss
new file mode 100644
index 00000000..54f00c1a
--- /dev/null
+++ b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.scss
@@ -0,0 +1,198 @@
+// ------------------------------------------------------------------
+// Layout
+// ------------------------------------------------------------------
+
+.o_AttachmentViewer {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ flex-flow: column;
+ align-items: center;
+ z-index: -1;
+}
+
+.o_AttachmentViewer_buttonNavigation {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 40px;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+.o_AttachmentViewer_buttonNavigationNext {
+ right: 15px;
+
+ > .fa {
+ margin: 1px 0 0 1px; // not correctly centered for some reasons
+ }
+}
+
+.o_AttachmentViewer_buttonNavigationPrevious {
+ left: 15px;
+
+ > .fa {
+ margin: 1px 1px 0 0; // not correctly centered for some reasons
+ }
+}
+
+.o_AttachmentViewer_header {
+ display: flex;
+ height: $o-navbar-height;
+ align-items: center;
+ padding: 0 15px;
+ width: 100%;
+}
+
+.o_AttachmentViewer_headerItem {
+ margin: 0 5px;
+
+ &:first-child {
+ margin-left: 0;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+}
+
+.o_AttachmentViewer_loading {
+ position: absolute;
+}
+
+.o_AttachmentViewer_main {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: -1;
+ padding: 45px 0;
+
+ &.o_with_img {
+ overflow: hidden;
+ }
+}
+
+.o_AttachmentViewer_toolbar {
+ position: absolute;
+ bottom: 45px;
+ transform: translateY(100%);
+ display: flex;
+}
+
+.o_AttachmentViewer_toolbarButton {
+ padding: 8px;
+}
+
+.o_AttachmentViewer_viewImage {
+ max-height: 100%;
+ max-width: 100%;
+}
+
+.o_AttachmentViewer_viewIframe {
+ width: 90%;
+ height: 100%;
+}
+
+.o_AttachmentViewer_viewVideo {
+ width: 75%;
+ height: 75%;
+}
+
+.o_AttachmentViewer_zoomer {
+ position: absolute;
+ padding: 45px 0;
+ height: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+// ------------------------------------------------------------------
+// Style
+// ------------------------------------------------------------------
+
+.o_AttachmentViewer {
+ outline: none;
+}
+
+.o_AttachmentViewer_buttonNavigation {
+ color: gray('400');
+ background-color: lighten(black, 15%);
+ border-radius: 100%;
+ cursor: pointer;
+
+ &:hover {
+ color: lighten(gray('400'), 15%);
+ background-color: black;
+ }
+}
+
+.o_AttachmentViewer_header {
+ background-color: rgba(0, 0, 0, 0.7);
+ color: gray('400');
+}
+
+.o_AttachmentViewer_headerItemButton {
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba(0, 0, 0, 0.8);
+ color: lighten(gray('400'), 15%);
+ }
+}
+
+.o_AttachmentViewer_headerItemButtonClose {
+ cursor: pointer;
+ font-size: 1.3rem;
+}
+
+.o_AttachmentViewer_toolbar {
+ cursor: pointer;
+}
+
+.o_AttachmentViewer_toolbarButton {
+ background-color: lighten(black, 15%);
+
+ &.o_disabled {
+ cursor: not-allowed;
+ filter: brightness(1.3);
+ }
+
+ &:not(.o_disabled) {
+ color: gray('400');
+ cursor: pointer;
+
+ &:hover {
+ background-color: black;
+ color: lighten(gray('400'), 15%);
+ }
+ }
+}
+
+.o_AttachmentViewer_view {
+ background-color: black;
+ box-shadow: 0 0 40px black;
+ outline: none;
+ border: none;
+
+ &.o_text {
+ background-color: white;
+ }
+}
+
+// ------------------------------------------------------------------
+// Animation
+// ------------------------------------------------------------------
+
+.o_AttachmentViewer_viewImage {
+ transition: transform 0.3s ease;
+}
+
diff --git a/addons/mail/static/src/components/attachment_viewer/attachment_viewer.xml b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.xml
new file mode 100644
index 00000000..8791bd09
--- /dev/null
+++ b/addons/mail/static/src/components/attachment_viewer/attachment_viewer.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+
+ <t t-name="mail.AttachmentViewer" owl="1">
+ <div class="o_AttachmentViewer" t-on-click="_onClick" t-on-keydown="_onKeydown" tabindex="0">
+ <div class="o_AttachmentViewer_header" t-on-click="_onClickHeader">
+ <t t-if="attachmentViewer.attachment.fileType">
+ <div class="o_AttachmentViewer_headerItem o_AttachmentViewer_icon">
+ <t t-if="attachmentViewer.attachment.fileType === 'image'">
+ <i class="fa fa-picture-o" role="img" title="Image"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.fileType === 'application/pdf'">
+ <i class="fa fa-file-text" role="img" title="PDF file"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.isTextFile">
+ <i class="fa fa-file-text" role="img" title="Text file"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.fileType === 'video'">
+ <i class="fa fa-video-camera" role="img" title="Video"/>
+ </t>
+ </div>
+ </t>
+ <div class="o_AttachmentViewer_headerItem o_AttachmentViewer_name">
+ <t t-esc="attachmentViewer.attachment.displayName"/>
+ </div>
+ <div class="o_AttachmentViewer_buttonDownload o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton" t-on-click="_onClickDownload" role="button" title="Download">
+ <i class="fa fa-download fa-fw" role="img"/>
+ </div>
+ <div class="o-autogrow"/>
+ <div class="o_AttachmentViewer_headerItem o_AttachmentViewer_headerItemButton o_AttachmentViewer_headerItemButtonClose" t-on-click="_onClickClose" role="button" title="Close (Esc)" aria-label="Close">
+ <i class="fa fa-fw fa-times" role="img"/>
+ </div>
+ </div>
+ <div class="o_AttachmentViewer_main" t-att-class="{ o_with_img: attachmentViewer.attachment.fileType === 'image' }" t-on-mousemove="_onMousemoveView">
+ <t t-if="attachmentViewer.attachment.fileType === 'image'">
+ <div class="o_AttachmentViewer_zoomer" t-ref="zoomer">
+ <t t-if="attachmentViewer.isImageLoading">
+ <div class="o_AttachmentViewer_loading">
+ <i class="fa fa-3x fa-circle-o-notch fa-fw fa-spin" role="img" title="Loading"/>
+ </div>
+ </t>
+ <img class="o_AttachmentViewer_view o_AttachmentViewer_viewImage" t-on-click="_onClickImage" t-on-mousedown="_onMousedownImage" t-on-wheel="_onWheelImage" t-on-load="_onLoadImage" t-att-src="attachmentViewer.attachment.defaultSource" t-att-style="imageStyle" draggable="false" alt="Viewer" t-key="'image_' + attachmentViewer.attachment.id" t-ref="image_{{ attachmentViewer.attachment.id }}"/>
+ </div>
+ </t>
+ <t t-if="attachmentViewer.attachment.fileType === 'application/pdf'">
+ <iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_viewPdf" t-att-src="attachmentViewer.attachment.defaultSource"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.isTextFile">
+ <iframe class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_text" t-att-src="attachmentViewer.attachment.defaultSource"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.fileType === 'youtu'">
+ <iframe allow="autoplay; encrypted-media" class="o_AttachmentViewer_view o_AttachmentViewer_viewIframe o_AttachmentViewer_youtube" t-att-src="attachmentViewer.attachment.defaultSource" height="315" width="560"/>
+ </t>
+ <t t-if="attachmentViewer.attachment.fileType === 'video'">
+ <video class="o_AttachmentViewer_view o_AttachmentViewer_viewVideo" t-on-click="_onClickVideo" controls="controls">
+ <source t-att-data-type="attachmentViewer.attachment.mimetype" t-att-src="attachmentViewer.attachment.defaultSource"/>
+ </video>
+ </t>
+ </div>
+ <t t-if="attachmentViewer.attachment.fileType === 'image'">
+ <div class="o_AttachmentViewer_toolbar" role="toolbar">
+ <div class="o_AttachmentViewer_toolbarButton" t-on-click="_onClickZoomIn" title="Zoom In (+)" role="button">
+ <i class="fa fa-fw fa-plus" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_toolbarButton" t-att-class="{ o_disabled: attachmentViewer.scale === 1 }" t-on-click="_onClickZoomReset" role="button" title="Reset Zoom (0)">
+ <i class="fa fa-fw fa-search" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_toolbarButton" t-att-class="{ o_disabled: attachmentViewer.scale === MIN_SCALE }" t-on-click="_onClickZoomOut" title="Zoom Out (-)" role="button">
+ <i class="fa fa-fw fa-minus" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_toolbarButton" t-on-click="_onClickRotate" title="Rotate (r)" role="button">
+ <i class="fa fa-fw fa-repeat" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_toolbarButton" t-on-click="_onClickPrint" title="Print" role="button">
+ <i class="fa fa-fw fa-print" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_buttonDownload o_AttachmentViewer_toolbarButton" t-on-click="_onClickDownload" title="Download" role="button">
+ <i class="fa fa-download fa-fw" role="img"/>
+ </div>
+ </div>
+ </t>
+ <t t-if="attachmentViewer.attachments.length > 1">
+ <div class="o_AttachmentViewer_buttonNavigation o_AttachmentViewer_buttonNavigationPrevious" t-on-click="_onClickPrevious" title="Previous (Left-Arrow)" role="button">
+ <span class="fa fa-chevron-left" role="img"/>
+ </div>
+ <div class="o_AttachmentViewer_buttonNavigation o_AttachmentViewer_buttonNavigationNext" t-on-click="_onClickNext" title="Next (Right-Arrow)" role="button">
+ <span class="fa fa-chevron-right" role="img"/>
+ </div>
+ </t>
+ </div>
+ </t>
+
+</templates>