Replace PDF.js with DevExpress DxPdfViewer
This commit replaces the existing PDF.js-based viewer with the DevExpress DxPdfViewer component, introducing significant improvements to the UI, state management, and signature handling. Key changes: - Integrated DevExpress.Blazor.PdfViewer and removed PDF.js dependencies. - Updated HTML structure to use `DxPdfViewer` and new overlay layers. - Refactored zoom and navigation logic to use DevExpress APIs. - Overhauled signature button rendering and positioning logic. - Added dynamic scaling for applied signatures based on zoom level. - Introduced `requestOverlayRefresh` for efficient overlay updates. - Added new CSS styles for the DevExpress viewer and overlays. - Refactored `pdf-viewer.js` to remove legacy PDF.js logic. - Improved performance with `requestAnimationFrame` and optimized event handling. - Added a `dispose` method for proper cleanup of resources. - Enhanced error handling and accessibility for signature buttons. - Removed redundant code and improved overall maintainability.
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
@using EnvelopeGenerator.Server.Client.Options
|
@using EnvelopeGenerator.Server.Client.Options
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
|
@using DevExpress.Blazor.PdfViewer
|
||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
@inject IOptions<PdfViewerOptions> PdfViewerOptions
|
@inject IOptions<PdfViewerOptions> PdfViewerOptions
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@@ -21,7 +22,6 @@
|
|||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
|
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf_viewer.min.css" rel="stylesheet" />
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
<script src="@AppVersion.GetVersionedUrl("js/pdf-viewer.js")"></script>
|
<script src="@AppVersion.GetVersionedUrl("js/pdf-viewer.js")"></script>
|
||||||
<script src="@AppVersion.GetVersionedUrl("js/receiver-signature.js")"></script>
|
<script src="@AppVersion.GetVersionedUrl("js/receiver-signature.js")"></script>
|
||||||
@@ -348,10 +348,15 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="pdf-canvas-wrapper">
|
<div class="pdf-canvas-wrapper">
|
||||||
<div class="pdf-page-container">
|
<div id="pdf-dx-viewer-host" class="envelope-dx-viewer-host">
|
||||||
<canvas id="pdf-canvas" class="pdf-canvas"></canvas>
|
@if (_pdfDocumentContent is not null && _pdfDocumentContent.Length > 0)
|
||||||
<div id="pdf-text-layer" class="pdf-text-layer"></div>
|
{
|
||||||
<div id="pdf-signature-layer" class="pdf-signature-layer"></div>
|
<DxPdfViewer CssClass="envelope-dx-pdf-viewer"
|
||||||
|
DocumentContent="@_pdfDocumentContent"
|
||||||
|
ZoomLevel="@_currentZoom"
|
||||||
|
IsSinglePagePreview="true" />
|
||||||
|
}
|
||||||
|
<div id="pdf-signature-layer" class="pdf-signature-layer pdf-signature-layer--dx"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -548,6 +553,7 @@
|
|||||||
bool _isLoading = true;
|
bool _isLoading = true;
|
||||||
string? _errorMessage;
|
string? _errorMessage;
|
||||||
string? _pdfDataUrl;
|
string? _pdfDataUrl;
|
||||||
|
byte[]? _pdfDocumentContent;
|
||||||
bool _pdfLoaded = false;
|
bool _pdfLoaded = false;
|
||||||
int _currentPage = 1;
|
int _currentPage = 1;
|
||||||
int _totalPages = 0;
|
int _totalPages = 0;
|
||||||
@@ -615,6 +621,7 @@
|
|||||||
|
|
||||||
if (pdfBytes is { Length: > 0 })
|
if (pdfBytes is { Length: > 0 })
|
||||||
{
|
{
|
||||||
|
_pdfDocumentContent = pdfBytes;
|
||||||
var base64 = Convert.ToBase64String(pdfBytes);
|
var base64 = Convert.ToBase64String(pdfBytes);
|
||||||
_pdfDataUrl = $"data:application/pdf;base64,{base64}";
|
_pdfDataUrl = $"data:application/pdf;base64,{base64}";
|
||||||
}
|
}
|
||||||
@@ -721,17 +728,18 @@
|
|||||||
options.ZoomStepPercentage
|
options.ZoomStepPercentage
|
||||||
});
|
});
|
||||||
|
|
||||||
var success = await JSRuntime.InvokeAsync<bool>("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl, _dotNetRef);
|
var success = await JSRuntime.InvokeAsync<bool>("pdfViewer.initialize", _pdfDataUrl, "pdf-dx-viewer-host", "pdf-signature-layer", _dotNetRef);
|
||||||
|
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
_pdfLoaded = true;
|
_pdfLoaded = true;
|
||||||
_totalPages = await JSRuntime.InvokeAsync<int>("pdfViewer.getTotalPages");
|
_totalPages = await JSRuntime.InvokeAsync<int>("pdfViewer.getTotalPages");
|
||||||
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
_currentPage = 1;
|
||||||
|
|
||||||
// Attach resize listeners
|
// Attach resize listeners
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.attachResizeListeners", _dotNetRef);
|
await JSRuntime.InvokeVoidAsync("pdfViewer.attachResizeListeners", _dotNetRef);
|
||||||
|
await JSRuntime.InvokeVoidAsync("pdfViewer.attachViewerInteractionListeners", "pdf-dx-viewer-host", _dotNetRef);
|
||||||
|
await JSRuntime.InvokeVoidAsync("pdfViewer.setViewState", _currentPage, _currentZoom);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
@@ -754,59 +762,44 @@
|
|||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
public async Task OnZoomChanged(double scale)
|
public async Task OnZoomChanged(double scale)
|
||||||
{
|
{
|
||||||
_currentZoom = (int)(scale * 100);
|
var requestedZoom = (int)Math.Round(scale * 100, MidpointRounding.AwayFromZero);
|
||||||
await InvokeAsync(StateHasChanged);
|
await SetZoom(requestedZoom);
|
||||||
|
|
||||||
// Small delay for canvas render to complete (reduced from 100ms to 10ms)
|
|
||||||
await Task.Delay(10);
|
|
||||||
await RenderSignatureButtonsAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task NextPage()
|
async Task NextPage()
|
||||||
{
|
{
|
||||||
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.nextPage"))
|
if (_currentPage >= _totalPages)
|
||||||
{
|
return;
|
||||||
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
|
||||||
await RenderSignatureButtonsAsync();
|
_currentPage++;
|
||||||
}
|
await ApplyViewerStateAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task PreviousPage()
|
async Task PreviousPage()
|
||||||
{
|
{
|
||||||
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.previousPage"))
|
if (_currentPage <= 1)
|
||||||
{
|
return;
|
||||||
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
|
||||||
await RenderSignatureButtonsAsync();
|
_currentPage--;
|
||||||
}
|
await ApplyViewerStateAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ZoomIn()
|
async Task ZoomIn()
|
||||||
{
|
{
|
||||||
if (_currentZoom >= 300) return;
|
if (_currentZoom >= 300) return;
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
|
await SetZoom(_currentZoom + PdfViewerOptions.Value.ZoomStepPercentage);
|
||||||
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
|
||||||
_currentZoom = (int)(scale * 100);
|
|
||||||
|
|
||||||
// Update signature overlay positions after zoom
|
|
||||||
await RenderSignatureButtonsAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ZoomOut()
|
async Task ZoomOut()
|
||||||
{
|
{
|
||||||
if (_currentZoom <= 50) return;
|
if (_currentZoom <= 50) return;
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
|
await SetZoom(_currentZoom - PdfViewerOptions.Value.ZoomStepPercentage);
|
||||||
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
|
||||||
_currentZoom = (int)(scale * 100);
|
|
||||||
|
|
||||||
// Update signature overlay positions after zoom
|
|
||||||
await RenderSignatureButtonsAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task SetZoom(int percentage)
|
async Task SetZoom(int percentage)
|
||||||
{
|
{
|
||||||
var scale = percentage / 100.0;
|
_currentZoom = Math.Clamp(percentage, 50, 300);
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.setScale", scale);
|
await ApplyViewerStateAsync();
|
||||||
_currentZoom = percentage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task OnZoomSliderChanged(ChangeEventArgs e)
|
async Task OnZoomSliderChanged(ChangeEventArgs e)
|
||||||
@@ -823,19 +816,16 @@
|
|||||||
async Task OnPageInputChanged(ChangeEventArgs e)
|
async Task OnPageInputChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
if (int.TryParse(e.Value?.ToString(), out var pageNum) && pageNum >= 1 && pageNum <= _totalPages)
|
if (int.TryParse(e.Value?.ToString(), out var pageNum) && pageNum >= 1 && pageNum <= _totalPages)
|
||||||
{
|
|
||||||
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.goToPage", pageNum))
|
|
||||||
{
|
{
|
||||||
_currentPage = pageNum;
|
_currentPage = pageNum;
|
||||||
}
|
await ApplyViewerStateAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task FitToWidth()
|
async Task FitToWidth()
|
||||||
{
|
{
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.fitToWidth");
|
_currentZoom = 150;
|
||||||
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
await ApplyViewerStateAsync();
|
||||||
_currentZoom = (int)(scale * 100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task ToggleThumbnails()
|
async Task ToggleThumbnails()
|
||||||
@@ -853,11 +843,11 @@
|
|||||||
|
|
||||||
async Task GoToPageFromThumbnail(int pageNum)
|
async Task GoToPageFromThumbnail(int pageNum)
|
||||||
{
|
{
|
||||||
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.goToPage", pageNum))
|
if (pageNum < 1 || pageNum > _totalPages)
|
||||||
{
|
return;
|
||||||
|
|
||||||
_currentPage = pageNum;
|
_currentPage = pageNum;
|
||||||
await RenderSignatureButtonsAsync();
|
await ApplyViewerStateAsync();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task RenderSignatureButtonsAsync()
|
async Task RenderSignatureButtonsAsync()
|
||||||
@@ -866,6 +856,7 @@
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("pdfViewer.setViewState", _currentPage, _currentZoom);
|
||||||
await JSRuntime.InvokeVoidAsync("pdfViewer.renderSignatureButtons", _signatures, _currentPage, _dotNetRef);
|
await JSRuntime.InvokeVoidAsync("pdfViewer.renderSignatureButtons", _signatures, _currentPage, _dotNetRef);
|
||||||
await UpdateSignatureCounterAsync();
|
await UpdateSignatureCounterAsync();
|
||||||
}
|
}
|
||||||
@@ -906,7 +897,13 @@
|
|||||||
public async Task OnPageChangedBySignatureNav(int newPage)
|
public async Task OnPageChangedBySignatureNav(int newPage)
|
||||||
{
|
{
|
||||||
_currentPage = newPage;
|
_currentPage = newPage;
|
||||||
await RenderSignatureButtonsAsync();
|
await ApplyViewerStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[JSInvokable]
|
||||||
|
public async Task OnZoomGestureRequested(int zoomPercentage)
|
||||||
|
{
|
||||||
|
await SetZoom(zoomPercentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task UpdateSignatureCounterAsync()
|
async Task UpdateSignatureCounterAsync()
|
||||||
@@ -1189,6 +1186,17 @@
|
|||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async Task ApplyViewerStateAsync()
|
||||||
|
{
|
||||||
|
if (!_pdfLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
await JSRuntime.InvokeVoidAsync("pdfViewer.setViewState", _currentPage, _currentZoom);
|
||||||
|
await Task.Delay(150);
|
||||||
|
await RenderSignatureButtonsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (_pdfLoaded)
|
if (_pdfLoaded)
|
||||||
|
|||||||
@@ -10,3 +10,4 @@
|
|||||||
@using EnvelopeGenerator.Server.Client
|
@using EnvelopeGenerator.Server.Client
|
||||||
@using EnvelopeGenerator.Server.Components
|
@using EnvelopeGenerator.Server.Components
|
||||||
@using DevExpress.Blazor
|
@using DevExpress.Blazor
|
||||||
|
@using DevExpress.Blazor.PdfViewer
|
||||||
|
|||||||
@@ -584,6 +584,32 @@ body.resizing {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.envelope-dx-viewer-host {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 720px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.envelope-dx-pdf-viewer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-pdf-viewer,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-pdfviewer,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-pdf-viewer-container,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-scroll-viewer,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-scroll-viewer-content,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-pdf-viewer-content,
|
||||||
|
.envelope-dx-pdf-viewer .dxbl-pdfviewer-content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pdf-page-container {
|
.pdf-page-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -641,6 +667,11 @@ body.resizing {
|
|||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pdf-signature-layer--dx {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pdf-signature-layer .signature-button {
|
.pdf-signature-layer .signature-button {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user