[REF] web, mail: rename attachment_viewer to file_viewer
Since the attachement viewer was moved to core, this renames it to file viewer to decouple it from the Attachment model/concept from the mail module. Part-of: odoo/odoo#119625
This commit is contained in:
@@ -5,7 +5,7 @@ import { Component } from "@odoo/owl";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { useAttachmentViewer } from "@web/core/attachment_viewer/attachment_viewer_hook";
|
||||
import { useFileViewer } from "@web/core/file_viewer/file_viewer_hook";
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { url } from "@web/core/utils/urls";
|
||||
@@ -25,7 +25,7 @@ export class AttachmentList extends Component {
|
||||
// Arbitrary high value, this is effectively a max-width.
|
||||
this.imagesWidth = 1920;
|
||||
this.dialog = useService("dialog");
|
||||
this.attachmentViewer = useAttachmentViewer();
|
||||
this.fileViewer = useFileViewer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
aria-label="View image"
|
||||
role="menuitem"
|
||||
t-att-data-mimetype="attachment.mimetype"
|
||||
t-on-click="() => this.attachmentViewer.open(attachment, props.attachments)"
|
||||
t-on-click="() => this.fileViewer.open(attachment, props.attachments)"
|
||||
>
|
||||
<img
|
||||
class="img img-fluid my-0 mx-auto o-viewable"
|
||||
@@ -52,7 +52,7 @@
|
||||
'o-viewable': attachment.isViewable,
|
||||
'o-isUploading': attachment.uploading,
|
||||
}" t-att-title="attachment.name ? attachment.name : undefined" role="menu" t-att-aria-label="attachment.name"
|
||||
t-on-click="() => this.attachmentViewer.open(attachment, props.attachments)"
|
||||
t-on-click="() => this.fileViewer.open(attachment, props.attachments)"
|
||||
>
|
||||
<!-- o_image from mimetype.scss -->
|
||||
<div class="o-mail-AttachmentCard-image o_image flex-shrink-0 m-1" role="menuitem" aria-label="Preview" t-att-tabindex="-1" t-att-aria-disabled="false" t-att-data-mimetype="attachment.mimetype"/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
.arrow {
|
||||
width: 5%;
|
||||
@include o-position-absolute(50%, 0);
|
||||
@include o-AttachmentViewer-arrow;
|
||||
@include o-FileViewer-arrow;
|
||||
transition: width 0.3s;
|
||||
padding-top: 30px;
|
||||
height: 75px;
|
||||
|
||||
@@ -15,7 +15,7 @@ $o-mail-Chatter-minWidth: 530px !default;
|
||||
$o-mail-Discuss-inspector: 250px !default;
|
||||
$o-mail-Discuss-headerHeight: 48px !default;
|
||||
|
||||
@mixin o-AttachmentViewer-arrow {
|
||||
@mixin o-FileViewer-arrow {
|
||||
background-color: rgba(black, 0.4);
|
||||
color: rgba(map-get($theme-colors, 'light'), 0.7);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
@include o-position-absolute(50%, $grid-gutter-width*0.5);
|
||||
border-radius: 100%;
|
||||
padding: 12px 16px 11px 18px;
|
||||
@include o-AttachmentViewer-arrow;
|
||||
@include o-FileViewer-arrow;
|
||||
}
|
||||
|
||||
.arrow-left {
|
||||
|
||||
@@ -109,12 +109,12 @@ patch(FormCompiler.prototype, "mail", {
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
// in sheet bg (attachment viewer present)
|
||||
setAttributes(webClientViewAttachmentViewHookXml, {
|
||||
"t-if": `__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-if": `__comp__.hasFileViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true);
|
||||
sheetBgChatterContainerHookXml.classList.add("o-isInFormSheetBg");
|
||||
setAttributes(sheetBgChatterContainerHookXml, {
|
||||
"t-if": `__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-if": `__comp__.hasFileViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
append(formSheetBgXml, sheetBgChatterContainerHookXml);
|
||||
const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector(
|
||||
@@ -128,12 +128,12 @@ patch(FormCompiler.prototype, "mail", {
|
||||
// after sheet bg (standard position, either aside or below)
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
"t-attf-class": `{{ __comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`,
|
||||
"t-if": `!(__comp__.hasFileViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
"t-attf-class": `{{ __comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasFileViewer() and __comp__.uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`,
|
||||
});
|
||||
setAttributes(chatterContainerXml, {
|
||||
isInFormSheetBg: "__comp__.hasAttachmentViewer()",
|
||||
hasMessageListScrollAdjust: `__comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
isInFormSheetBg: "__comp__.hasFileViewer()",
|
||||
hasMessageListScrollAdjust: `__comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasFileViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
});
|
||||
} else {
|
||||
setAttributes(chatterContainerXml, {
|
||||
|
||||
@@ -33,7 +33,7 @@ patch(FormRenderer.prototype, "mail", {
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAttachmentViewer() {
|
||||
hasFileViewer() {
|
||||
if (!this.threadService || this.uiService.size < SIZES.XXL || !this.props.record.resId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ QUnit.test("view attachment", async (assert) => {
|
||||
await openDiscuss(channelId);
|
||||
assert.containsOnce($, ".o-mail-AttachmentImage img");
|
||||
await click(".o-mail-AttachmentImage");
|
||||
assert.containsOnce($, ".o-AttachmentViewer");
|
||||
assert.containsOnce($, ".o-FileViewer");
|
||||
});
|
||||
|
||||
QUnit.test("close attachment viewer", async (assert) => {
|
||||
@@ -140,10 +140,10 @@ QUnit.test("close attachment viewer", async (assert) => {
|
||||
assert.containsOnce($, ".o-mail-AttachmentImage img");
|
||||
|
||||
await click(".o-mail-AttachmentImage");
|
||||
assert.containsOnce($, ".o-AttachmentViewer");
|
||||
assert.containsOnce($, ".o-FileViewer");
|
||||
|
||||
await click(".o-AttachmentViewer div[aria-label='Close']");
|
||||
assert.containsNone($, ".o-AttachmentViewer");
|
||||
await click(".o-FileViewer div[aria-label='Close']");
|
||||
assert.containsNone($, ".o-FileViewer");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
@@ -177,8 +177,8 @@ QUnit.test(
|
||||
const { openDiscuss } = await start();
|
||||
await openDiscuss(channelId);
|
||||
await click(".o-mail-AttachmentImage");
|
||||
const image = $(".o-AttachmentViewer-viewImage")[0];
|
||||
await click(".o-AttachmentViewer div[aria-label='Close']");
|
||||
const image = $(".o-FileViewer-viewImage")[0];
|
||||
await click(".o-FileViewer div[aria-label='Close']");
|
||||
// Simulate image becoming loaded.
|
||||
let successfulLoad;
|
||||
try {
|
||||
@@ -314,10 +314,10 @@ QUnit.test(
|
||||
|
||||
click(".o-mail-AttachmentCard:contains(test.odt)").catch(() => {});
|
||||
await nextTick();
|
||||
assert.containsNone($, ".o-AttachmentViewer");
|
||||
assert.containsNone($, ".o-FileViewer");
|
||||
|
||||
await click(".o-mail-AttachmentImage[title='test.png']");
|
||||
assert.containsOnce($, ".o-AttachmentViewer");
|
||||
assert.containsOnce($, ".o-FileViewer");
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -136,16 +136,16 @@ QUnit.test("view attachments", async (assert) => {
|
||||
views: [[false, "form"]],
|
||||
});
|
||||
await click('.o-mail-AttachmentCard[aria-label="Blah.txt"] .o-mail-AttachmentCard-image');
|
||||
assert.containsOnce($, ".o-AttachmentViewer");
|
||||
assert.containsOnce($, ".o-AttachmentViewer-header:contains(Blah.txt)");
|
||||
assert.containsOnce($, ".o-AttachmentViewer div[aria-label='Next']");
|
||||
assert.containsOnce($, ".o-FileViewer");
|
||||
assert.containsOnce($, ".o-FileViewer-header:contains(Blah.txt)");
|
||||
assert.containsOnce($, ".o-FileViewer div[aria-label='Next']");
|
||||
|
||||
await click(".o-AttachmentViewer div[aria-label='Next']");
|
||||
assert.containsOnce($, ".o-AttachmentViewer-header:contains(Blu.txt)");
|
||||
assert.containsOnce($, ".o-AttachmentViewer div[aria-label='Next']");
|
||||
await click(".o-FileViewer div[aria-label='Next']");
|
||||
assert.containsOnce($, ".o-FileViewer-header:contains(Blu.txt)");
|
||||
assert.containsOnce($, ".o-FileViewer div[aria-label='Next']");
|
||||
|
||||
await click(".o-AttachmentViewer div[aria-label='Next']");
|
||||
assert.containsOnce($, ".o-AttachmentViewer-header:contains(Blah.txt)");
|
||||
await click(".o-FileViewer div[aria-label='Next']");
|
||||
assert.containsOnce($, ".o-FileViewer-header:contains(Blah.txt)");
|
||||
});
|
||||
|
||||
QUnit.test("scroll to attachment box when toggling on", async (assert) => {
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
import { CANCEL_GLOBAL_CLICK, KanbanRecord } from "@web/views/kanban/kanban_record";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useAttachmentViewer } from "@web/core/attachment_viewer/attachment_viewer_hook";
|
||||
import { useFileViewer } from "@web/core/file_viewer/file_viewer_hook";
|
||||
|
||||
export class MrpDocumentsKanbanRecord extends KanbanRecord {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.attachmentService = useService("mail.attachment");
|
||||
this.attachmentViewer = useAttachmentViewer();
|
||||
this.fileViewer = useFileViewer();
|
||||
}
|
||||
/**
|
||||
* @override
|
||||
@@ -29,7 +29,7 @@ export class MrpDocumentsKanbanRecord extends KanbanRecord {
|
||||
name: this.props.record.data.name,
|
||||
mimetype: this.props.record.data.mimetype,
|
||||
});
|
||||
this.attachmentViewer.open(attachment)
|
||||
this.fileViewer.open(attachment)
|
||||
return;
|
||||
}
|
||||
return super.onGlobalClick(...arguments);
|
||||
|
||||
@@ -231,12 +231,12 @@ QUnit.module('MrpDocumentsKanbanView', {
|
||||
assert.containsOnce(target, ".o_kanban_previewer");
|
||||
|
||||
await click(target.querySelector(".o_kanban_previewer"));
|
||||
await waitUntil('.o-AttachmentViewer');
|
||||
assert.containsOnce(target, '.o-AttachmentViewer');
|
||||
assert.containsOnce(target, '.o-AttachmentViewer-headerButton .fa-times');
|
||||
await waitUntil('.o-FileViewer');
|
||||
assert.containsOnce(target, '.o-FileViewer');
|
||||
assert.containsOnce(target, '.o-FileViewer-headerButton .fa-times');
|
||||
|
||||
await click(target, '.o-AttachmentViewer-headerButton .fa-times');
|
||||
assert.containsNone(target, '.o-AttachmentViewer');
|
||||
await click(target, '.o-FileViewer-headerButton .fa-times');
|
||||
assert.containsNone(target, '.o-FileViewer');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -185,7 +185,7 @@ This module provides the core of the Odoo Web Client.
|
||||
('remove', 'web/static/src/webclient/navbar/navbar.scss'), # already in assets_common
|
||||
('remove', 'web/static/src/webclient/clickbot/clickbot.js'), # lazy loaded
|
||||
('remove', 'web/static/src/views/form/button_box/*.scss'),
|
||||
('remove', 'web/static/src/core/attachment_viewer/attachment_viewer.dark.scss'),
|
||||
('remove', 'web/static/src/core/file_viewer/file_viewer.dark.scss'),
|
||||
|
||||
# remove the report code and whitelist only what's needed
|
||||
('remove', 'web/static/src/webclient/actions/reports/**/*'),
|
||||
@@ -400,7 +400,7 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/core/**/*',
|
||||
('remove', 'web/static/src/core/commands/**/*'),
|
||||
('remove', 'web/static/src/core/debug/debug_menu.js'),
|
||||
('remove', 'web/static/src/core/attachment_viewer/attachment_viewer.dark.scss'),
|
||||
('remove', 'web/static/src/core/file_viewer/file_viewer.dark.scss'),
|
||||
'web/static/src/public/error_notifications.js',
|
||||
|
||||
'web/static/src/legacy/utils.js',
|
||||
@@ -483,7 +483,7 @@ This module provides the core of the Odoo Web Client.
|
||||
# ---------------------------------------------------------------------
|
||||
"web.dark_mode_assets_common": [
|
||||
('include', 'web.assets_common'),
|
||||
'web/static/src/core/attachment_viewer/attachment_viewer.dark.scss',
|
||||
'web/static/src/core/file_viewer/file_viewer.dark.scss',
|
||||
],
|
||||
"web.dark_mode_assets_backend": [
|
||||
('include', 'web.assets_backend'),
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.AttachmentViewer" owl="1">
|
||||
<div class="d-flex justify-content-center" t-att-class="{ 'modal fixed-top bottom-0': props.modal }">
|
||||
<div class="o-AttachmentViewer flex-column align-items-center d-flex w-100 h-100" tabindex="0">
|
||||
<div class="o-AttachmentViewer-header position-absolute top-0 d-flex w-100 bg-black-75 text-400">
|
||||
<div t-if="isViewable" class="d-flex align-items-center ms-4 me-2">
|
||||
<i t-if="state.attachment.isImage" class="fa fa-picture-o" role="img" title="Image"/>
|
||||
<i t-if="state.attachment.isPdf" class="fa fa-file-text" role="img" title="PDF file"/>
|
||||
<i t-if="state.attachment.isText" class="fa fa-file-text" role="img" title="Text file"/>
|
||||
<i t-if="state.attachment.isVideo" class="fa fa-video-camera" role="img" title="Video"/>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mx-2 overflow-auto">
|
||||
<span class="text-truncate" t-out="state.attachment.displayName"/>
|
||||
</div>
|
||||
<div class="flex-grow-1"/>
|
||||
<div class="o-AttachmentViewer-download o-AttachmentViewer-headerButton d-flex align-items-center px-3 cursor-pointer" role="button" title="Download">
|
||||
<a t-att-href="state.attachment.downloadUrl" class="text-reset" download="">
|
||||
<i class="fa fa-download fa-fw" role="img"/>
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div t-on-click.stop="close" class="o-AttachmentViewer-headerButton d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer" role="button" title="Close (Esc)" aria-label="Close">
|
||||
<i class="fa fa-fw fa-times" role="img"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-on-click.stop="close" t-on-mousemove="onMousemoveView" 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': state.attachment.isImage }">
|
||||
<div t-if="state.attachment.isImage" class="o-AttachmentViewer-zoomer position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="zoomer">
|
||||
<div t-if="!state.imageLoaded" class="position-absolute">
|
||||
<i class="fa fa-3x fa-circle-o-notch fa-fw fa-spin text-white" role="img" title="Loading"/>
|
||||
</div>
|
||||
<img t-on-click.stop="" t-on-load="onImageLoaded" t-on-wheel="onWheelImage" t-on-mousedown.stop="onMousedownImage" t-on-mouseup.stop="onMouseupImage" class="o-AttachmentViewer-view o-AttachmentViewer-viewImage mw-100 mh-100 transition-base" t-att-src="state.attachment.defaultSource" t-att-style="imageStyle" draggable="false" alt="Viewer" t-ref="image"/>
|
||||
</div>
|
||||
<iframe t-if="state.attachment.isPdf" class="o-AttachmentViewer-view w-75 h-100 border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': ui.isSmall }" t-att-src="state.attachment.defaultSource"/>
|
||||
<iframe t-if="state.attachment.isText" class="o-AttachmentViewer-view o-isText o_text w-75 h-100 border-0" t-att-src="state.attachment.defaultSource"/>
|
||||
<iframe t-if="state.attachment.isUrlYoutube" allow="autoplay; encrypted-media" class="o-AttachmentViewer-view w-75 h-100 border-0" t-att-src="state.attachment.defaultSource" height="315" width="560"/>
|
||||
<video t-if="state.attachment.isVideo" class="o-AttachmentViewer-view w-75 h-75" t-att-class="{ 'w-100 h-100': ui.isSmall }" t-on-click.stop="" controls="controls">
|
||||
<source t-att-data-type="state.attachment.mimetype" t-att-src="state.attachment.defaultSource"/>
|
||||
</video>
|
||||
</div>
|
||||
<div t-if="state.attachment.isImage" class="position-absolute bottom-0 d-flex" role="toolbar">
|
||||
<div class="o-AttachmentViewer-toolbarButton p-3 rounded-0" t-on-click="zoomIn" title="Zoom In (+)" role="button">
|
||||
<i class="fa fa-fw fa-plus" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': state.scale === 1 }" t-on-click="resetZoom" role="button" title="Reset Zoom (0)">
|
||||
<i class="fa fa-fw fa-search" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': state.scale === minScale }" t-on-click="zoomOut" title="Zoom Out (-)" role="button">
|
||||
<i class="fa fa-fw fa-minus" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-toolbarButton p-3 rounded-0" t-on-click="rotate" title="Rotate (r)" role="button">
|
||||
<i class="fa fa-fw fa-repeat" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-toolbarButton p-3 rounded-0" t-on-click="onClickPrint" title="Print" role="button">
|
||||
<i class="fa fa-fw fa-print" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-download o-AttachmentViewer-toolbarButton p-3 rounded-0 cursor-pointer" title="Download" role="button">
|
||||
<a t-att-href="state.attachment.downloadUrl" class="text-reset" download="">
|
||||
<i class="fa fa-download fa-fw" role="img"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="props.attachments.length > 1">
|
||||
<div class="o-AttachmentViewer-navigation 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="previous" title="Previous (Left-Arrow)" aria-label="Previous" role="button">
|
||||
<span class="oi oi-chevron-left" role="img"/>
|
||||
</div>
|
||||
<div class="o-AttachmentViewer-navigation position-absolute top-0 bottom-0 end-0 align-items-center justify-content-center d-flex my-auto me-3 rounded-circle" t-on-click="next" title="Next (Right-Arrow)" aria-label="Next" role="button">
|
||||
<span class="oi oi-chevron-right" role="img"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,34 +0,0 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { onWillDestroy } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { AttachmentViewer } from "./attachment_viewer";
|
||||
|
||||
let id = 1;
|
||||
|
||||
export function useAttachmentViewer() {
|
||||
const attachmentViewerId = `web.attachment_viewer${id++}`;
|
||||
/**
|
||||
* @param {import("@mail/attachments/attachment_model").Attachment} attachment
|
||||
* @param {import("@mail/attachments/attachment_model").Attachment[]} attachments
|
||||
*/
|
||||
function open(attachment, attachments = [attachment]) {
|
||||
if (!attachment.isViewable) {
|
||||
return;
|
||||
}
|
||||
if (attachments.length > 0) {
|
||||
const viewableAttachments = attachments.filter((attachment) => attachment.isViewable);
|
||||
const index = viewableAttachments.indexOf(attachment);
|
||||
registry.category("main_components").add(attachmentViewerId, {
|
||||
Component: AttachmentViewer,
|
||||
props: { attachments: viewableAttachments, startIndex: index, close },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
registry.category("main_components").remove(attachmentViewerId);
|
||||
}
|
||||
onWillDestroy(close);
|
||||
return { open, close };
|
||||
}
|
||||
+2
-2
@@ -2,6 +2,6 @@
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o-AttachmentViewer-toolbarButton {
|
||||
--web-attachment-viewer-toolbarButton-background-color: #{$o-gray-200};
|
||||
.o-FileViewer-toolbarButton {
|
||||
--file-viewer-toolbarButton-background-color: #{$o-gray-200};
|
||||
}
|
||||
+26
-12
@@ -4,14 +4,28 @@ import { Component, useExternalListener, useRef, useState } from "@odoo/owl";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
/**
|
||||
* @typedef {Object} File
|
||||
* @property {string} displayName
|
||||
* @property {string} downloadUrl
|
||||
* @property {boolean} [isImage]
|
||||
* @property {boolean} [isPdf]
|
||||
* @property {boolean} [isVideo]
|
||||
* @property {boolean} [isText]
|
||||
* @property {string} [defaultSource]
|
||||
* @property {boolean} [isUrlYoutube]
|
||||
* @property {string} [mimetype]
|
||||
* @property {boolean} [isViewable]
|
||||
* @typedef {Object} Props
|
||||
* @property {Array<T>} attachments
|
||||
* @property {Array<File>} files
|
||||
* @property {number} startIndex
|
||||
* @property {function} close
|
||||
* @property {boolean} [modal]
|
||||
* @extends {Component<Props, Env>}
|
||||
*/
|
||||
export class AttachmentViewer extends Component {
|
||||
static template = "web.AttachmentViewer";
|
||||
export class FileViewer extends Component {
|
||||
static template = "web.FileViewer";
|
||||
static components = {};
|
||||
static props = ["attachments", "startIndex", "close", "modal?"];
|
||||
static props = ["files", "startIndex", "close", "modal?"];
|
||||
static defaultProps = {
|
||||
modal: true,
|
||||
};
|
||||
@@ -37,7 +51,7 @@ export class AttachmentViewer extends Component {
|
||||
useExternalListener(document, "keydown", this.onKeydown);
|
||||
this.state = useState({
|
||||
index: this.props.startIndex,
|
||||
attachment: this.props.attachments[this.props.startIndex],
|
||||
file: this.props.files[this.props.startIndex],
|
||||
imageLoaded: false,
|
||||
scale: 1,
|
||||
angle: 0,
|
||||
@@ -54,18 +68,18 @@ export class AttachmentViewer extends Component {
|
||||
}
|
||||
|
||||
next() {
|
||||
const last = this.props.attachments.length - 1;
|
||||
this.activateAttachment(this.state.index === last ? 0 : this.state.index + 1);
|
||||
const last = this.props.files.length - 1;
|
||||
this.activateFile(this.state.index === last ? 0 : this.state.index + 1);
|
||||
}
|
||||
|
||||
previous() {
|
||||
const last = this.props.attachments.length - 1;
|
||||
this.activateAttachment(this.state.index === 0 ? last : this.state.index - 1);
|
||||
const last = this.props.files.length - 1;
|
||||
this.activateFile(this.state.index === 0 ? last : this.state.index - 1);
|
||||
}
|
||||
|
||||
activateAttachment(index) {
|
||||
activateFile(index) {
|
||||
this.state.index = index;
|
||||
this.state.attachment = this.props.attachments[index];
|
||||
this.state.file = this.props.files[index];
|
||||
}
|
||||
|
||||
onKeydown(ev) {
|
||||
@@ -228,7 +242,7 @@ export class AttachmentViewer extends Component {
|
||||
</script>
|
||||
</head>
|
||||
<body onload='onloadImage()'>
|
||||
<img src="${this.state.attachment.defaultSource}" alt=""/>
|
||||
<img src="${this.state.file.defaultSource}" alt=""/>
|
||||
</body>
|
||||
</html>`);
|
||||
printWindow.document.close();
|
||||
+9
-9
@@ -1,33 +1,33 @@
|
||||
.o-AttachmentViewer {
|
||||
.o-FileViewer {
|
||||
z-index: -1;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-navigation {
|
||||
.o-FileViewer-navigation {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-header {
|
||||
.o-FileViewer-header {
|
||||
height: $o-navbar-height;
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-main {
|
||||
.o-FileViewer-main {
|
||||
z-index: -1;
|
||||
padding: ($o-navbar-height * 1.125) 0;
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-zoomer {
|
||||
.o-FileViewer-zoomer {
|
||||
padding: ($o-navbar-height * 1.125) 0;
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-headerButton:hover {
|
||||
.o-FileViewer-headerButton:hover {
|
||||
background-color: rgba($white, 0.1);
|
||||
color: lighten($gray-400, 15%);
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-toolbarButton {
|
||||
background-color: var(--web-attachment-viewer-toolbarButton-background-color, #{$o-gray-800});
|
||||
.o-FileViewer-toolbarButton {
|
||||
background-color: var(--file-viewer-toolbarButton-background-color, #{$o-gray-800});
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o-AttachmentViewer-view {
|
||||
.o-FileViewer-view {
|
||||
background-color: #000000;
|
||||
box-shadow: 0 0 40px #000000;
|
||||
outline: none;
|
||||
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.FileViewer" owl="1">
|
||||
<div class="d-flex justify-content-center" t-att-class="{ 'modal fixed-top bottom-0': props.modal }">
|
||||
<div class="o-FileViewer flex-column align-items-center d-flex w-100 h-100" tabindex="0">
|
||||
<div class="o-FileViewer-header position-absolute top-0 d-flex w-100 bg-black-75 text-400">
|
||||
<div t-if="isViewable" class="d-flex align-items-center ms-4 me-2">
|
||||
<i t-if="state.file.isImage" class="fa fa-picture-o" role="img" title="Image"/>
|
||||
<i t-if="state.file.isPdf" class="fa fa-file-text" role="img" title="PDF file"/>
|
||||
<i t-if="state.file.isText" class="fa fa-file-text" role="img" title="Text file"/>
|
||||
<i t-if="state.file.isVideo" class="fa fa-video-camera" role="img" title="Video"/>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mx-2 overflow-auto">
|
||||
<span class="text-truncate" t-out="state.file.displayName"/>
|
||||
</div>
|
||||
<div class="flex-grow-1"/>
|
||||
<div class="o-FileViewer-download o-FileViewer-headerButton d-flex align-items-center px-3 cursor-pointer" role="button" title="Download">
|
||||
<a t-att-href="state.file.downloadUrl" class="text-reset" download="">
|
||||
<i class="fa fa-download fa-fw" role="img"/>
|
||||
<span>Download</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div t-on-click.stop="close" class="o-FileViewer-headerButton d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer" role="button" title="Close (Esc)" aria-label="Close">
|
||||
<i class="fa fa-fw fa-times" role="img"/>
|
||||
</div>
|
||||
</div>
|
||||
<div t-on-click.stop="close" t-on-mousemove="onMousemoveView" class="o-FileViewer-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': state.file.isImage }">
|
||||
<div t-if="state.file.isImage" class="o-FileViewer-zoomer position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="zoomer">
|
||||
<div t-if="!state.imageLoaded" class="position-absolute">
|
||||
<i class="fa fa-3x fa-circle-o-notch fa-fw fa-spin text-white" role="img" title="Loading"/>
|
||||
</div>
|
||||
<img t-on-click.stop="" t-on-load="onImageLoaded" t-on-wheel="onWheelImage" t-on-mousedown.stop="onMousedownImage" t-on-mouseup.stop="onMouseupImage" class="o-FileViewer-view o-FileViewer-viewImage mw-100 mh-100 transition-base" t-att-src="state.file.defaultSource" t-att-style="imageStyle" draggable="false" alt="Viewer" t-ref="image"/>
|
||||
</div>
|
||||
<iframe t-if="state.file.isPdf" class="o-FileViewer-view w-75 h-100 border-0" t-ref="iframeViewerPdf" t-att-class="{ 'w-100': ui.isSmall }" t-att-src="state.file.defaultSource"/>
|
||||
<iframe t-if="state.file.isText" class="o-FileViewer-view o-isText o_text w-75 h-100 border-0" t-att-src="state.file.defaultSource"/>
|
||||
<iframe t-if="state.file.isUrlYoutube" allow="autoplay; encrypted-media" class="o-FileViewer-view w-75 h-100 border-0" t-att-src="state.file.defaultSource" height="315" width="560"/>
|
||||
<video t-if="state.file.isVideo" class="o-FileViewer-view w-75 h-75" t-att-class="{ 'w-100 h-100': ui.isSmall }" t-on-click.stop="" controls="controls">
|
||||
<source t-att-data-type="state.file.mimetype" t-att-src="state.file.defaultSource"/>
|
||||
</video>
|
||||
</div>
|
||||
<div t-if="state.file.isImage" class="position-absolute bottom-0 d-flex" role="toolbar">
|
||||
<div class="o-FileViewer-toolbarButton p-3 rounded-0" t-on-click="zoomIn" title="Zoom In (+)" role="button">
|
||||
<i class="fa fa-fw fa-plus" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': state.scale === 1 }" t-on-click="resetZoom" role="button" title="Reset Zoom (0)">
|
||||
<i class="fa fa-fw fa-search" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-toolbarButton p-3 rounded-0" t-att-class="{ 'o_disabled opacity-50': state.scale === minScale }" t-on-click="zoomOut" title="Zoom Out (-)" role="button">
|
||||
<i class="fa fa-fw fa-minus" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-toolbarButton p-3 rounded-0" t-on-click="rotate" title="Rotate (r)" role="button">
|
||||
<i class="fa fa-fw fa-repeat" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-toolbarButton p-3 rounded-0" t-on-click="onClickPrint" title="Print" role="button">
|
||||
<i class="fa fa-fw fa-print" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-download o-FileViewer-toolbarButton p-3 rounded-0 cursor-pointer" title="Download" role="button">
|
||||
<a t-att-href="state.file.downloadUrl" class="text-reset" download="">
|
||||
<i class="fa fa-download fa-fw" role="img"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="props.files.length > 1">
|
||||
<div class="o-FileViewer-navigation 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="previous" title="Previous (Left-Arrow)" aria-label="Previous" role="button">
|
||||
<span class="oi oi-chevron-left" role="img"/>
|
||||
</div>
|
||||
<div class="o-FileViewer-navigation position-absolute top-0 bottom-0 end-0 align-items-center justify-content-center d-flex my-auto me-3 rounded-circle" t-on-click="next" title="Next (Right-Arrow)" aria-label="Next" role="button">
|
||||
<span class="oi oi-chevron-right" role="img"/>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,34 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { onWillDestroy } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { FileViewer } from "./file_viewer";
|
||||
|
||||
let id = 1;
|
||||
|
||||
export function useFileViewer() {
|
||||
const fileViewerId = `web.file_viewer${id++}`;
|
||||
/**
|
||||
* @param {import("@web/core/file_viewer/file_viewer").FileViewer.props.files[]} file
|
||||
* @param {import("@web/core/file_viewer/file_viewer").FileViewer.props.files} files
|
||||
*/
|
||||
function open(file, files = [file]) {
|
||||
if (!file.isViewable) {
|
||||
return;
|
||||
}
|
||||
if (files.length > 0) {
|
||||
const viewableFiles = files.filter((file) => file.isViewable);
|
||||
const index = viewableFiles.indexOf(file);
|
||||
registry.category("main_components").add(fileViewerId, {
|
||||
Component: FileViewer,
|
||||
props: { files: viewableFiles, startIndex: index, close },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
registry.category("main_components").remove(fileViewerId);
|
||||
}
|
||||
onWillDestroy(close);
|
||||
return { open, close };
|
||||
}
|
||||
Reference in New Issue
Block a user