changes
This commit is contained in:
@@ -5,9 +5,27 @@ import { _t } from "@web/core/l10n/translation";
|
||||
import { session } from "@web/session";
|
||||
import { rpc } from "@web/core/network/rpc";
|
||||
import { user } from "@web/core/user";
|
||||
import { Component, useState, onMounted } from "@odoo/owl";
|
||||
import { Component, useState, onMounted, onWillUnmount } from "@odoo/owl";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
|
||||
|
||||
/**
|
||||
* Fire-and-forget audit log for PDF preview/print/download actions.
|
||||
*/
|
||||
async function logPreviewAction(reportName, actionType, recordIds, modelName) {
|
||||
try {
|
||||
await rpc("/fusion_pdf_preview/log_action", {
|
||||
report_name: reportName,
|
||||
action_type: actionType,
|
||||
record_ids: recordIds || '',
|
||||
model_name: modelName || '',
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("Failed to log PDF preview action:", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class PDFViewerDialog extends Component {
|
||||
setup() {
|
||||
this.state = useState({
|
||||
@@ -15,10 +33,20 @@ export class PDFViewerDialog extends Component {
|
||||
viewerUrl: this.getViewerUrl(),
|
||||
isMaximized: false
|
||||
});
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', this._onKeyDown);
|
||||
});
|
||||
|
||||
onWillUnmount(() => {
|
||||
document.removeEventListener('keydown', this._onKeyDown);
|
||||
});
|
||||
}
|
||||
|
||||
getViewerUrl() {
|
||||
const baseUrl = '/pdf_print_preview/static/lib/pdfjs/web/viewer.html';
|
||||
const baseUrl = '/fusion_pdf_preview/static/lib/pdfjs/web/viewer.html';
|
||||
return `${baseUrl}?file=${this.props.url}`;
|
||||
}
|
||||
|
||||
@@ -43,20 +71,83 @@ export class PDFViewerDialog extends Component {
|
||||
}
|
||||
return 'height: calc(90vh - 100px) !important;';
|
||||
}
|
||||
|
||||
downloadPDF() {
|
||||
const link = document.createElement('a');
|
||||
link.href = this.props.url;
|
||||
link.download = this.props.title ? `${this.props.title}.pdf` : 'document.pdf';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
if (this.props.reportName) {
|
||||
logPreviewAction(this.props.reportName, 'download',
|
||||
this.props.recordIds, this.props.modelName);
|
||||
}
|
||||
}
|
||||
|
||||
openInNewTab() {
|
||||
window.open(this.props.url, '_blank');
|
||||
}
|
||||
|
||||
printPDF() {
|
||||
try {
|
||||
const iframe = this.el?.querySelector('iframe');
|
||||
if (iframe && iframe.contentWindow) {
|
||||
iframe.contentWindow.print();
|
||||
}
|
||||
} catch (err) {
|
||||
window.open(this.props.url, '_blank');
|
||||
}
|
||||
|
||||
if (this.props.reportName) {
|
||||
logPreviewAction(this.props.reportName, 'print',
|
||||
this.props.recordIds, this.props.modelName);
|
||||
}
|
||||
}
|
||||
|
||||
_onKeyDown(ev) {
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const ctrlOrCmd = isMac ? ev.metaKey : ev.ctrlKey;
|
||||
|
||||
// F key (no modifiers) - toggle fullscreen
|
||||
if ((ev.key === 'f' || ev.key === 'F') && !ctrlOrCmd && !ev.altKey && !ev.shiftKey) {
|
||||
const activeEl = document.activeElement;
|
||||
if (activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'TEXTAREA' || activeEl.isContentEditable)) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
// Ctrl+P / Cmd+P - print
|
||||
if (ctrlOrCmd && (ev.key === 'p' || ev.key === 'P')) {
|
||||
ev.preventDefault();
|
||||
this.printPDF();
|
||||
}
|
||||
|
||||
// Ctrl+D / Cmd+D - download
|
||||
if (ctrlOrCmd && (ev.key === 'd' || ev.key === 'D')) {
|
||||
ev.preventDefault();
|
||||
this.downloadPDF();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PDFViewerDialog.template = 'pdf_print_preview.PDFViewerDialog';
|
||||
PDFViewerDialog.template = 'fusion_pdf_preview.PDFViewerDialog';
|
||||
PDFViewerDialog.components = { Dialog };
|
||||
|
||||
// Register for use in actions
|
||||
registry.category("dialog").add("PDFViewerDialog", PDFViewerDialog);
|
||||
|
||||
|
||||
export function openPDFViewer(env, url, title = "PDF Document") {
|
||||
export function openPDFViewer(env, url, title = "PDF Document", meta = {}) {
|
||||
const dialog = env.services.dialog;
|
||||
return dialog.add(PDFViewerDialog, {
|
||||
url: url,
|
||||
title: title
|
||||
title: title,
|
||||
reportName: meta.reportName || '',
|
||||
recordIds: meta.recordIds || '',
|
||||
modelName: meta.modelName || '',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,7 +161,7 @@ function handleAutomaticPrinting(url, env) {
|
||||
const printFrame = document.createElement('iframe');
|
||||
printFrame.style.display = 'none';
|
||||
printFrame.src = url;
|
||||
|
||||
|
||||
printFrame.onload = function() {
|
||||
try {
|
||||
printFrame.contentWindow.print();
|
||||
@@ -88,12 +179,14 @@ function handleAutomaticPrinting(url, env) {
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
document.body.removeChild(printFrame);
|
||||
if (printFrame.parentNode) {
|
||||
document.body.removeChild(printFrame);
|
||||
}
|
||||
window.removeEventListener('focus', cleanup);
|
||||
};
|
||||
|
||||
window.addEventListener('focus', cleanup);
|
||||
|
||||
|
||||
document.body.appendChild(printFrame);
|
||||
}
|
||||
|
||||
@@ -103,15 +196,17 @@ function handleAutomaticPrinting(url, env) {
|
||||
*
|
||||
* @private
|
||||
* @param {ReportAction} action
|
||||
* @param {env} env
|
||||
* @param {Object} env
|
||||
* @param {string} filename
|
||||
* @returns {string}
|
||||
*/
|
||||
function _getReportUrl(action, env, filename) {
|
||||
let url = `/report/pdf/${action.report_name}`;
|
||||
const actionContext = action.context || {};
|
||||
filename = filename || action.name;
|
||||
if(filename !== undefined)
|
||||
if (filename !== undefined) {
|
||||
filename = filename.replace(/[/?%#&=]/g, "_") + ".pdf";
|
||||
}
|
||||
if (action.data && JSON.stringify(action.data) !== "{}") {
|
||||
const options = encodeURIComponent(JSON.stringify(action.data));
|
||||
const context = encodeURIComponent(JSON.stringify(actionContext));
|
||||
@@ -125,7 +220,7 @@ function _getReportUrl(action, env, filename) {
|
||||
return url;
|
||||
}
|
||||
|
||||
async function PdfPrintPreview(action, options, env) {
|
||||
async function FusionPdfPreview(action, options, env) {
|
||||
const link = '<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>';
|
||||
const WKHTMLTOPDF_MESSAGES = {
|
||||
broken:
|
||||
@@ -149,15 +244,17 @@ async function PdfPrintPreview(action, options, env) {
|
||||
),
|
||||
};
|
||||
|
||||
if (action.report_type === "qweb-pdf" && env.services.menu.getCurrentApp() !== undefined && (session.preview_print || session.automatic_printing)) {
|
||||
let getReportResult = rpc("/pdf_print_preview/get_report_name", {
|
||||
if (action.report_type === "qweb-pdf" && env.services.menu.getCurrentApp() !== undefined) {
|
||||
const userWantsPreview = session.preview_print;
|
||||
const userWantsAutoPrint = session.automatic_printing;
|
||||
|
||||
const result = await rpc("/fusion_pdf_preview/get_report_name", {
|
||||
report_name: action.report_name,
|
||||
data: JSON.stringify(action.context)
|
||||
});
|
||||
const result = await getReportResult;
|
||||
const state = result["wkhtmltopdf_state"];
|
||||
const previewMode = result["fusion_preview_mode"] || "default";
|
||||
|
||||
// display a notification according to wkhtmltopdf's state
|
||||
if (state in WKHTMLTOPDF_MESSAGES) {
|
||||
env.services.notification.add(WKHTMLTOPDF_MESSAGES[state], {
|
||||
sticky: true,
|
||||
@@ -166,14 +263,40 @@ async function PdfPrintPreview(action, options, env) {
|
||||
}
|
||||
|
||||
if (state === "upgrade" || state === "ok") {
|
||||
let url = _getReportUrl(action, env, result["file_name"]);
|
||||
if(session.preview_print) {
|
||||
// PreviewDialog.createPreviewDialog(self, url, action.name);
|
||||
openPDFViewer(env, url, action.name);
|
||||
// Determine effective behavior based on report-level override
|
||||
let shouldPreview = userWantsPreview;
|
||||
let shouldAutoPrint = userWantsAutoPrint;
|
||||
|
||||
if (previewMode === 'preview') {
|
||||
shouldPreview = true;
|
||||
shouldAutoPrint = false;
|
||||
} else if (previewMode === 'download') {
|
||||
return false;
|
||||
} else if (previewMode === 'auto_print') {
|
||||
shouldPreview = false;
|
||||
shouldAutoPrint = true;
|
||||
}
|
||||
|
||||
if (session.automatic_printing) {
|
||||
if (!shouldPreview && !shouldAutoPrint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const url = _getReportUrl(action, env, result["file_name"]);
|
||||
const actionContext = action.context || {};
|
||||
const meta = {
|
||||
reportName: action.report_name,
|
||||
recordIds: (actionContext.active_ids || []).join(','),
|
||||
modelName: actionContext.active_model || '',
|
||||
};
|
||||
|
||||
if (shouldPreview) {
|
||||
openPDFViewer(env, url, action.name, meta);
|
||||
logPreviewAction(meta.reportName, 'preview', meta.recordIds, meta.modelName);
|
||||
}
|
||||
|
||||
if (shouldAutoPrint) {
|
||||
handleAutomaticPrinting(url, env);
|
||||
logPreviewAction(meta.reportName, 'print', meta.recordIds, meta.modelName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -182,4 +305,4 @@ async function PdfPrintPreview(action, options, env) {
|
||||
|
||||
registry
|
||||
.category("ir.actions.report handlers")
|
||||
.add("pdf_print_preview", PdfPrintPreview);
|
||||
.add("fusion_pdf_preview", FusionPdfPreview);
|
||||
|
||||
@@ -13,7 +13,7 @@ function reportPreviewConfigItem(env) {
|
||||
description: _t("Report preview"),
|
||||
callback: async function () {
|
||||
const actionDescription = await rpc("/web/action/load", {
|
||||
action_id: "pdf_print_preview.action_short_preview_print"
|
||||
action_id: "fusion_pdf_preview.action_short_preview_print"
|
||||
});
|
||||
actionDescription.res_id = user.userId;
|
||||
env.services.action.doAction(actionDescription);
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="pdf_print_preview.PDFViewerDialog">
|
||||
<t t-name="fusion_pdf_preview.PDFViewerDialog">
|
||||
<Dialog size="getDialogSize()" footer="false">
|
||||
<t t-set-slot="header">
|
||||
<div class="d-flex align-items-center justify-content-between flex w-100">
|
||||
<div style="width: 90px"></div>
|
||||
<div style="width: 135px"></div>
|
||||
<h4 class="modal-title text-break fw-normal">
|
||||
<b><t t-esc="props.title" /></b>
|
||||
</h4>
|
||||
<div class="d-flex align-items-center" style="width: 45px">
|
||||
<button type="button" class="btn" t-on-click="toggle">
|
||||
<div class="d-flex align-items-center" style="width: 135px; justify-content: flex-end;">
|
||||
<button type="button" class="btn" t-on-click="downloadPDF" title="Download (Ctrl+D)">
|
||||
<i class="fa fa-download"/>
|
||||
</button>
|
||||
<button type="button" class="btn" t-on-click="openInNewTab" title="Open in new tab">
|
||||
<i class="fa fa-external-link"/>
|
||||
</button>
|
||||
<button type="button" class="btn" t-on-click="toggle" title="Toggle fullscreen (F)">
|
||||
<i t-if="!state.isMaximized" class="fa fa-square-o" />
|
||||
<i t-else="" class="fa fa-clone" />
|
||||
</button>
|
||||
@@ -17,17 +23,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
<div class="o_pdf_viewer position-relative">
|
||||
<!-- Loading spinner -->
|
||||
<div t-if="state.isLoading"
|
||||
class="position-absolute w-100 h-100 d-flex justify-content-center align-items-center"
|
||||
<div t-if="state.isLoading"
|
||||
class="position-absolute w-100 h-100 d-flex justify-content-center align-items-center"
|
||||
style="z-index: 1;">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- PDF.js viewer iframe -->
|
||||
<iframe t-att-src="state.viewerUrl"
|
||||
class="w-100 border-0"
|
||||
|
||||
Reference in New Issue
Block a user