Files
Odoo-Modules/fusion_pdf_preview/static/src/js/pdf_preview.js
gsinghpal fc3c966484 changes
2026-03-13 12:38:28 -04:00

309 lines
9.6 KiB
JavaScript

/** @odoo-module **/
import { registry } from "@web/core/registry";
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, 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({
isLoading: true,
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 = '/fusion_pdf_preview/static/lib/pdfjs/web/viewer.html';
return `${baseUrl}?file=${this.props.url}`;
}
onIframeLoad() {
this.state.isLoading = false;
}
toggle() {
this.state.isMaximized = !this.state.isMaximized;
}
getDialogSize() {
if (this.state.isMaximized) {
return 'fullscreen';
}
return 'xl';
}
getFrameStyle() {
if (this.state.isMaximized) {
return 'height: calc(98vh - 141px) !important;';
}
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 = 'fusion_pdf_preview.PDFViewerDialog';
PDFViewerDialog.components = { Dialog };
registry.category("dialog").add("PDFViewerDialog", PDFViewerDialog);
export function openPDFViewer(env, url, title = "PDF Document", meta = {}) {
const dialog = env.services.dialog;
return dialog.add(PDFViewerDialog, {
url: url,
title: title,
reportName: meta.reportName || '',
recordIds: meta.recordIds || '',
modelName: meta.modelName || '',
});
}
/**
* Helper function to handle automatic printing
* @param {string} url - URL of the PDF to print
* @param {Object} env - Environment object for notifications
*/
function handleAutomaticPrinting(url, env) {
const printFrame = document.createElement('iframe');
printFrame.style.display = 'none';
printFrame.src = url;
printFrame.onload = function() {
try {
printFrame.contentWindow.print();
} catch (err) {
env.services.notification.add(
_t("Failed to print automatically. Please check your browser settings."),
{
type: 'warning',
sticky: true,
title: _t("Printing Error"),
}
);
document.body.removeChild(printFrame);
}
};
const cleanup = () => {
if (printFrame.parentNode) {
document.body.removeChild(printFrame);
}
window.removeEventListener('focus', cleanup);
};
window.addEventListener('focus', cleanup);
document.body.appendChild(printFrame);
}
/**
* Generates the report url given a report action.
*
* @private
* @param {ReportAction} action
* @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) {
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));
url += `?filename=${filename}&options=${options}&context=${context}&`;
} else {
if (actionContext.active_ids) {
url += `/${actionContext.active_ids.join(",")}?filename=${filename}&context=${encodeURIComponent(JSON.stringify(user.context))}&`;
}
}
return url;
}
async function FusionPdfPreview(action, options, env) {
const link = '<br><br><a href="http://wkhtmltopdf.org/" target="_blank">wkhtmltopdf.org</a>';
const WKHTMLTOPDF_MESSAGES = {
broken:
_t(
"Your installation of Wkhtmltopdf seems to be broken. The report will be shown " +
"in html."
) + link,
install:
_t(
"Unable to find Wkhtmltopdf on this system. The report will be shown in " + "html."
) + link,
upgrade:
_t(
"You should upgrade your version of Wkhtmltopdf to at least 0.12.0 in order to " +
"get a correct display of headers and footers as well as support for " +
"table-breaking between pages."
) + link,
workers: _t(
"You need to start Odoo with at least two workers to print a pdf version of " +
"the reports."
),
};
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 state = result["wkhtmltopdf_state"];
const previewMode = result["fusion_preview_mode"] || "default";
if (state in WKHTMLTOPDF_MESSAGES) {
env.services.notification.add(WKHTMLTOPDF_MESSAGES[state], {
sticky: true,
title: _t("Report"),
});
}
if (state === "upgrade" || state === "ok") {
// 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 (!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;
}
}
}
registry
.category("ir.actions.report handlers")
.add("fusion_pdf_preview", FusionPdfPreview);