Introduced the ability to download PDFs in the `SenderPdfImageEditor` component: - Added `PdfFileName` parameter to specify the file name. - Implemented `CanDownloadPdf` property to enable/disable downloads. - Created `OnDownloadPdfAsync` method to handle download logic via JS interop. Updated `SenderPdfImageEditorToolbar` to support downloads: - Added `CanDownloadPdf` and `DownloadPdf` parameters. - Enabled "Download PDF" button based on `CanDownloadPdf`. - Added `DownloadPdfAsync` method to trigger the download callback. Enhanced `SenderEditorPage` to pass `PdfFileName` to `SenderPdfImageEditor`. Added `downloadDataUrlAsFile` function in `envelope-editor.js` to facilitate file downloads using a temporary `<a>` element. Made minor adjustments for consistency, including adding `ZoomStepPercent` to the toolbar and refining `SetZoomPercent` logic.
248 lines
8.2 KiB
JavaScript
248 lines
8.2 KiB
JavaScript
window.envelopeEditor = {
|
|
|
|
_moveThrottle: {
|
|
rafId: 0,
|
|
latest: null,
|
|
waiters: []
|
|
},
|
|
|
|
/**
|
|
* Returns the click position normalised to [0,1] relative to the rendered PDF page
|
|
* element inside DxPdfViewer (or DxReportViewer as fallback).
|
|
*
|
|
* Normalising means the result is independent of zoom level: no matter how much the
|
|
* user has zoomed in/out, the same physical spot on the PDF will always yield the same
|
|
* normalised value. C# multiplies by the page's point dimensions to get PDF points.
|
|
*
|
|
* @param {string} viewerCssClass - CssClass set on DxPdfViewer (e.g. "sender-editor-pdf-viewer")
|
|
* @param {number} clientX - MouseEvent.clientX from Blazor
|
|
* @param {number} clientY - MouseEvent.clientY from Blazor
|
|
* @returns {{ normX, normY, pageIndex } | null}
|
|
* normX / normY : 0..1 fraction within the page element
|
|
* pageIndex : 0-based index of the page the click landed on (-1 if not found)
|
|
*/
|
|
getClickCoordsOnPdfPage: function (viewerCssClass, clientX, clientY) {
|
|
|
|
// Find the viewer root element
|
|
const viewer = document.querySelector('.' + viewerCssClass);
|
|
if (!viewer) {
|
|
console.warn('[envelopeEditor] viewer not found for class:', viewerCssClass);
|
|
return null;
|
|
}
|
|
|
|
// --- Candidate page elements (ordered by preference) ---
|
|
// Sender editor custom renderer uses data-editor-page blocks.
|
|
// Legacy Dx viewers remain as fallback selectors.
|
|
const pageSelectors = [
|
|
'[data-editor-page]',
|
|
'.dxbl-pdfv-page',
|
|
'.dxbrv-report-preview-page',
|
|
'.dxbrv-report-preview-content-img',
|
|
];
|
|
|
|
let allPages = [];
|
|
for (const sel of pageSelectors) {
|
|
const found = Array.from(viewer.querySelectorAll(sel));
|
|
if (found.length > 0) {
|
|
allPages = found;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allPages.length === 0) {
|
|
console.warn('[envelopeEditor] no page elements found inside viewer');
|
|
return null;
|
|
}
|
|
|
|
// --- Find which page the click landed on ---
|
|
// Walk through all pages; pick the one whose bounding rect contains the click.
|
|
// If none contains it exactly, fall back to the page closest vertically.
|
|
let targetPage = null;
|
|
let targetIndex = -1;
|
|
let minDist = Infinity;
|
|
|
|
for (let i = 0; i < allPages.length; i++) {
|
|
const rect = allPages[i].getBoundingClientRect();
|
|
|
|
// Exact hit
|
|
if (clientX >= rect.left && clientX <= rect.right &&
|
|
clientY >= rect.top && clientY <= rect.bottom) {
|
|
targetPage = allPages[i];
|
|
targetIndex = i;
|
|
break;
|
|
}
|
|
|
|
// Track closest page (vertical centre distance) as fallback
|
|
const cy = rect.top + rect.height / 2;
|
|
const dist = Math.abs(clientY - cy);
|
|
if (dist < minDist) {
|
|
minDist = dist;
|
|
targetPage = allPages[i];
|
|
targetIndex = i;
|
|
}
|
|
}
|
|
|
|
if (!targetPage) return null;
|
|
|
|
const pageRect = targetPage.getBoundingClientRect();
|
|
|
|
// Clamp click inside page boundaries before normalising
|
|
const clampedX = Math.max(pageRect.left, Math.min(clientX, pageRect.right));
|
|
const clampedY = Math.max(pageRect.top, Math.min(clientY, pageRect.bottom));
|
|
|
|
const normX = (clampedX - pageRect.left) / pageRect.width;
|
|
const normY = (clampedY - pageRect.top) / pageRect.height;
|
|
|
|
return {
|
|
normX: normX,
|
|
normY: normY,
|
|
pageIndex: targetIndex,
|
|
pageNumber: parseInt(targetPage.getAttribute('data-editor-page') || String(targetIndex + 1), 10)
|
|
};
|
|
},
|
|
|
|
getClickCoordsOnPdfPageThrottled: function (viewerCssClass, clientX, clientY) {
|
|
return new Promise((resolve) => {
|
|
const state = window.envelopeEditor._moveThrottle;
|
|
state.latest = { viewerCssClass, clientX, clientY };
|
|
state.waiters.push(resolve);
|
|
|
|
if (state.rafId !== 0) {
|
|
return;
|
|
}
|
|
|
|
state.rafId = window.requestAnimationFrame(() => {
|
|
state.rafId = 0;
|
|
|
|
const args = state.latest;
|
|
state.latest = null;
|
|
|
|
const result = args
|
|
? window.envelopeEditor.getClickCoordsOnPdfPage(args.viewerCssClass, args.clientX, args.clientY)
|
|
: null;
|
|
|
|
const waiters = state.waiters.splice(0, state.waiters.length);
|
|
for (const waiter of waiters) {
|
|
waiter(result);
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
scrollToPdfPage: function (viewerCssClass, pageNumber) {
|
|
const viewer = document.querySelector('.' + viewerCssClass);
|
|
if (!viewer) {
|
|
return;
|
|
}
|
|
|
|
const targetPageNumber = Math.max(1, Number(pageNumber) || 1);
|
|
const page = viewer.querySelector('[data-editor-page="' + targetPageNumber + '"]');
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
const viewerRect = viewer.getBoundingClientRect();
|
|
const pageRect = page.getBoundingClientRect();
|
|
const targetScrollTop = viewer.scrollTop + (pageRect.top - viewerRect.top) - 8;
|
|
|
|
viewer.scrollTo({
|
|
top: Math.max(0, targetScrollTop),
|
|
behavior: 'smooth'
|
|
});
|
|
},
|
|
|
|
getVisiblePdfPageNumber: function (viewerCssClass) {
|
|
const viewer = document.querySelector('.' + viewerCssClass);
|
|
if (!viewer) {
|
|
return null;
|
|
}
|
|
|
|
const pages = Array.from(viewer.querySelectorAll('[data-editor-page]'));
|
|
if (pages.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const viewerRect = viewer.getBoundingClientRect();
|
|
let bestPage = null;
|
|
let bestVisibleHeight = -1;
|
|
|
|
for (const page of pages) {
|
|
const rect = page.getBoundingClientRect();
|
|
const visibleTop = Math.max(rect.top, viewerRect.top);
|
|
const visibleBottom = Math.min(rect.bottom, viewerRect.bottom);
|
|
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
|
|
if (visibleHeight > bestVisibleHeight) {
|
|
bestVisibleHeight = visibleHeight;
|
|
bestPage = page;
|
|
}
|
|
}
|
|
|
|
if (!bestPage) {
|
|
return null;
|
|
}
|
|
|
|
const raw = bestPage.getAttribute('data-editor-page');
|
|
const parsed = Number.parseInt(raw || '0', 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
},
|
|
|
|
scrollPdfViewerByDelta: function (viewerCssClass, deltaY) {
|
|
const viewer = document.querySelector('.' + viewerCssClass);
|
|
if (!viewer) {
|
|
return;
|
|
}
|
|
|
|
viewer.scrollTop += Number(deltaY) || 0;
|
|
},
|
|
|
|
ensureZoomOverride: function (viewerCssClass) {
|
|
const viewer = document.querySelector('.' + viewerCssClass);
|
|
if (!viewer) {
|
|
return;
|
|
}
|
|
|
|
if (viewer.dataset.zoomOverrideAttached === '1') {
|
|
return;
|
|
}
|
|
|
|
viewer.dataset.zoomOverrideAttached = '1';
|
|
|
|
viewer.addEventListener('wheel', function (event) {
|
|
event.preventDefault();
|
|
}, { passive: false });
|
|
|
|
viewer.addEventListener('keydown', function (event) {
|
|
if (!event.ctrlKey) {
|
|
return;
|
|
}
|
|
|
|
const key = event.key;
|
|
if (key === '+' || key === '=' || key === '-' || key === '0') {
|
|
event.preventDefault();
|
|
}
|
|
});
|
|
|
|
viewer.addEventListener('touchmove', function () {
|
|
}, { passive: true });
|
|
},
|
|
|
|
downloadDataUrlAsFile: function (dataUrl, fileName, mimeType) {
|
|
if (!dataUrl || typeof dataUrl !== 'string') {
|
|
return;
|
|
}
|
|
|
|
const downloadName = fileName || 'document.pdf';
|
|
|
|
const link = document.createElement('a');
|
|
link.href = dataUrl;
|
|
link.download = downloadName;
|
|
link.rel = 'noopener';
|
|
|
|
const host = document.body || document.documentElement;
|
|
host.appendChild(link);
|
|
link.click();
|
|
host.removeChild(link);
|
|
}
|
|
};
|