diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor new file mode 100644 index 00000000..97ee3e6e --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor @@ -0,0 +1,376 @@ +@page "/envelope/{EnvelopeKey}" +@using EnvelopeGenerator.ReceiverUI.Services +@using Microsoft.Extensions.Options +@using EnvelopeGenerator.ReceiverUI.Options +@using Microsoft.JSInterop +@inject DocumentService DocumentService +@inject NavigationManager Navigation +@inject IOptions AppOptions +@inject IJSRuntime JSRuntime +@implements IAsyncDisposable + + + + + +
+
+
+
+ +
+
Dokumentenansicht
+
ID: @EnvelopeKey
+
+
+ @if (_pdfLoaded) { +
+
+ + @(_currentZoom)% + +
+
+ + Seite @_currentPage / @_totalPages + +
+
+ } +
+
+ +
+ @if (_isLoading) { +
+
+
+ Lädt... +
+

Dokument wird geladen...

+
+
+ } else if (_errorMessage is not null) { +
+
+
+ + + + +
+
Fehler beim Laden des Dokuments
+

@_errorMessage

+
+
+
+
+ } else if (!string.IsNullOrWhiteSpace(_pdfDataUrl)) { +
+
+ +
+
+ } else { +
+
+
+ + + + Dokument konnte nicht geladen werden. +
+
+
+ } +
+
+ + + +@code { + [Parameter] public string? EnvelopeKey { get; set; } + + bool _isLoading = true; + string? _errorMessage; + string? _pdfDataUrl; + bool _pdfLoaded = false; + int _currentPage = 1; + int _totalPages = 0; + int _currentZoom = 150; + + protected override async Task OnInitializedAsync() { + if (string.IsNullOrWhiteSpace(EnvelopeKey)) { + _errorMessage = "Envelope-Schlüssel fehlt."; + _isLoading = false; + return; + } + + try { + var (pdfBytes, statusCode) = await DocumentService.GetDocumentAsync(EnvelopeKey); + + if (pdfBytes is { Length: > 0 }) { + var base64 = Convert.ToBase64String(pdfBytes); + _pdfDataUrl = $"data:application/pdf;base64,{base64}"; + Console.WriteLine($"PDF data ready, length: {_pdfDataUrl.Length}"); + } else { + _errorMessage = $"Dokument konnte nicht geladen werden. HTTP Status: {statusCode}"; + } + } catch (Exception ex) { + _errorMessage = $"Fehler: {ex.Message}"; + } + + _isLoading = false; + + // CRITICAL: Force re-render after data is loaded + await InvokeAsync(StateHasChanged); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) { + Console.WriteLine($"OnAfterRenderAsync called - firstRender: {firstRender}, _pdfDataUrl: {(_pdfDataUrl != null ? "exists" : "null")}, _pdfLoaded: {_pdfLoaded}"); + + // Only initialize once when data is ready and not already loaded + if (!_pdfLoaded && !string.IsNullOrWhiteSpace(_pdfDataUrl)) { + // Wait for DOM and scripts + await Task.Delay(500); + + try { + Console.WriteLine("Calling pdfViewer.initialize..."); + var success = await JSRuntime.InvokeAsync("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl); + Console.WriteLine($"Initialize returned: {success}"); + + if (success) { + _pdfLoaded = true; + _totalPages = await JSRuntime.InvokeAsync("pdfViewer.getTotalPages"); + _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); + Console.WriteLine($"PDF loaded - Pages: {_totalPages}, Current: {_currentPage}"); + await InvokeAsync(StateHasChanged); + } + } catch (Exception ex) { + Console.WriteLine($"Exception: {ex.Message}\nStack: {ex.StackTrace}"); + _errorMessage = $"PDF.js Fehler: {ex.Message}"; + await InvokeAsync(StateHasChanged); + } + } + } + + async Task NextPage() { + if (await JSRuntime.InvokeAsync("pdfViewer.nextPage")) { + _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); + } + } + + async Task PreviousPage() { + if (await JSRuntime.InvokeAsync("pdfViewer.previousPage")) { + _currentPage = await JSRuntime.InvokeAsync("pdfViewer.getCurrentPage"); + } + } + + async Task ZoomIn() { + await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn"); + var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale"); + _currentZoom = (int)(scale * 100); + } + + async Task ZoomOut() { + await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut"); + var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale"); + _currentZoom = (int)(scale * 100); + } + + public async ValueTask DisposeAsync() { + // Cleanup if needed + } +} diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js new file mode 100644 index 00000000..6d97f3cf --- /dev/null +++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js @@ -0,0 +1,168 @@ +// PDF.js Viewer for Blazor WASM +window.pdfViewer = { + pdfDoc: null, + pageNum: 1, + pageRendering: false, + pageNumPending: null, + scale: 1.5, + canvas: null, + ctx: null, + totalPages: 0, + + async initialize(canvasId, pdfDataUrl) { + try { + console.log('PDF.js initialization started for canvas:', canvasId); + + // Wait for PDF.js to load + if (typeof window.pdfjsLib === 'undefined') { + console.error('PDF.js library not loaded, waiting...'); + await this.waitForPdfJs(); + } + + const pdfjsLib = window.pdfjsLib; + pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js'; + + this.canvas = document.getElementById(canvasId); + if (!this.canvas) { + console.error('Canvas element not found:', canvasId); + return false; + } + + console.log('Canvas element found, loading PDF...'); + + this.ctx = this.canvas.getContext('2d'); + + // Load PDF from data URL + const uint8Array = this.base64ToUint8Array(pdfDataUrl); + console.log('PDF data converted to Uint8Array, size:', uint8Array.length); + + const loadingTask = pdfjsLib.getDocument({ data: uint8Array }); + this.pdfDoc = await loadingTask.promise; + this.totalPages = this.pdfDoc.numPages; + + console.log('PDF loaded successfully, total pages:', this.totalPages); + + // Render first page + await this.renderPage(this.pageNum); + return true; + } catch (error) { + console.error('Error initializing PDF viewer:', error); + return false; + } + }, + + async waitForPdfJs() { + for (let i = 0; i < 50; i++) { + if (typeof window.pdfjsLib !== 'undefined') { + console.log('PDF.js loaded after', i * 100, 'ms'); + return; + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error('PDF.js failed to load after 5 seconds'); + }, + + base64ToUint8Array(base64) { + // Remove data URL prefix if present + const base64String = base64.includes(',') ? base64.split(',')[1] : base64; + const raw = atob(base64String); + const uint8Array = new Uint8Array(raw.length); + for (let i = 0; i < raw.length; i++) { + uint8Array[i] = raw.charCodeAt(i); + } + return uint8Array; + }, + + async renderPage(num) { + this.pageRendering = true; + + try { + const page = await this.pdfDoc.getPage(num); + const viewport = page.getViewport({ scale: this.scale }); + + console.log('Rendering page:', num, 'Viewport:', viewport.width, 'x', viewport.height); + + this.canvas.height = viewport.height; + this.canvas.width = viewport.width; + + const renderContext = { + canvasContext: this.ctx, + viewport: viewport + }; + + await page.render(renderContext).promise; + + console.log('Page rendered successfully'); + + this.pageRendering = false; + + if (this.pageNumPending !== null) { + this.renderPage(this.pageNumPending); + this.pageNumPending = null; + } + } catch (error) { + console.error('Error rendering page:', error); + this.pageRendering = false; + } + }, + + queueRenderPage(num) { + if (this.pageRendering) { + this.pageNumPending = num; + } else { + this.renderPage(num); + } + }, + + nextPage() { + if (this.pageNum >= this.totalPages) { + return false; + } + this.pageNum++; + this.queueRenderPage(this.pageNum); + return true; + }, + + previousPage() { + if (this.pageNum <= 1) { + return false; + } + this.pageNum--; + this.queueRenderPage(this.pageNum); + return true; + }, + + goToPage(num) { + if (num < 1 || num > this.totalPages) { + return false; + } + this.pageNum = num; + this.queueRenderPage(this.pageNum); + return true; + }, + + zoomIn() { + this.scale += 0.25; + this.queueRenderPage(this.pageNum); + }, + + zoomOut() { + if (this.scale > 0.5) { + this.scale -= 0.25; + this.queueRenderPage(this.pageNum); + } + }, + + getCurrentPage() { + return this.pageNum; + }, + + getTotalPages() { + return this.totalPages; + }, + + getScale() { + return this.scale; + } +}; +