Simplified the component by removing unnecessary debug output and redundant comments. Removed the `OnAfterRenderAsync` method, which previously handled PDF viewer initialization and related JavaScript calls. This suggests the initialization logic has been moved elsewhere or is no longer required in this component.
366 lines
13 KiB
Plaintext
366 lines
13 KiB
Plaintext
@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<ApiOptions> AppOptions
|
|
@inject IJSRuntime JSRuntime
|
|
@implements IAsyncDisposable
|
|
|
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
|
<script src="js/pdf-viewer.js"></script>
|
|
|
|
<div class="envelope-viewer-layout">
|
|
<div class="envelope-action-bar">
|
|
<div class="envelope-action-bar__inner">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="envelope-logo">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5L14 4.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5h-2z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<div class="envelope-title">Dokumentenansicht</div>
|
|
<div class="envelope-key">ID: @EnvelopeKey</div>
|
|
</div>
|
|
</div>
|
|
@if (_pdfLoaded) {
|
|
<div class="d-flex align-items-center gap-2 ms-auto">
|
|
<div class="pdf-controls">
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="ZoomOut" title="Zoom Out">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M6.5 1A5.5 5.5 0 0 0 1 6.5v3A5.5 5.5 0 0 0 6.5 15h3a5.5 5.5 0 0 0 5.5-5.5v-3A5.5 5.5 0 0 0 9.5 1h-3zM4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8z"/>
|
|
</svg>
|
|
</button>
|
|
<span class="zoom-level">@(_currentZoom)%</span>
|
|
<button class="btn btn-sm btn-outline-primary" @onclick="ZoomIn" title="Zoom In">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M6.5 1A5.5 5.5 0 0 0 1 6.5v3A5.5 5.5 0 0 0 6.5 15h3a5.5 5.5 0 0 0 5.5-5.5v-3A5.5 5.5 0 0 0 9.5 1h-3zM8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<div class="pdf-navigation">
|
|
<button class="btn btn-sm btn-primary" @onclick="PreviousPage" disabled="@(_currentPage <= 1)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
|
</svg>
|
|
</button>
|
|
<span class="page-info">Seite @_currentPage / @_totalPages</span>
|
|
<button class="btn btn-sm btn-primary" @onclick="NextPage" disabled="@(_currentPage >= _totalPages)">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="envelope-content">
|
|
@if (_isLoading) {
|
|
<div class="d-flex justify-content-center align-items-center h-100">
|
|
<div class="text-center">
|
|
<div class="spinner-border text-white mb-3" style="width: 3.5rem; height: 3.5rem;" role="status">
|
|
<span class="visually-hidden">Lädt...</span>
|
|
</div>
|
|
<p class="text-white fw-semibold">Dokument wird geladen...</p>
|
|
</div>
|
|
</div>
|
|
} else if (_errorMessage is not null) {
|
|
<div class="error-container">
|
|
<div class="alert alert-danger shadow-lg">
|
|
<div class="d-flex align-items-start">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="me-3 flex-shrink-0" viewBox="0 0 16 16">
|
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
|
<path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0zM7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995z"/>
|
|
</svg>
|
|
<div>
|
|
<h5 class="mb-2">Fehler beim Laden des Dokuments</h5>
|
|
<p class="mb-0">@_errorMessage</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
} else if (!string.IsNullOrWhiteSpace(_pdfDataUrl)) {
|
|
<div class="pdf-viewer-container">
|
|
<div class="pdf-frame">
|
|
<canvas id="pdf-canvas" class="pdf-canvas"></canvas>
|
|
</div>
|
|
</div>
|
|
} else {
|
|
<div class="error-container">
|
|
<div class="alert alert-warning shadow-lg">
|
|
<div class="d-flex align-items-center">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" fill="currentColor" class="me-3" viewBox="0 0 16 16">
|
|
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
|
|
</svg>
|
|
<span class="fs-5">Dokument konnte nicht geladen werden.</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.envelope-viewer-layout {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 50%, #7e22ce 100%);
|
|
}
|
|
|
|
.envelope-action-bar {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
backdrop-filter: blur(20px);
|
|
border-bottom: 3px solid rgba(126, 34, 206, 0.3);
|
|
padding: 1.25rem 2rem;
|
|
flex-shrink: 0;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.envelope-action-bar__inner {
|
|
max-width: 1600px;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2rem;
|
|
}
|
|
|
|
.envelope-logo svg {
|
|
filter: drop-shadow(0 2px 4px rgba(126, 34, 206, 0.3));
|
|
color: #7e22ce;
|
|
}
|
|
|
|
.envelope-title {
|
|
font-size: 1.125rem;
|
|
font-weight: 700;
|
|
color: #1e293b;
|
|
letter-spacing: -0.025em;
|
|
}
|
|
|
|
.envelope-key {
|
|
font-size: 0.8125rem;
|
|
color: #64748b;
|
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
font-weight: 500;
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.pdf-controls, .pdf-navigation {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.zoom-level, .page-info {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: #475569;
|
|
min-width: 60px;
|
|
text-align: center;
|
|
}
|
|
|
|
.envelope-content {
|
|
flex: 1;
|
|
min-height: 0;
|
|
padding: 1.5rem;
|
|
position: relative;
|
|
overflow: auto;
|
|
}
|
|
|
|
.pdf-viewer-container {
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
padding: 0;
|
|
}
|
|
|
|
.pdf-frame {
|
|
background: white;
|
|
border-radius: 16px;
|
|
box-shadow:
|
|
0 25px 50px -12px rgba(0, 0, 0, 0.25),
|
|
0 0 0 1px rgba(255, 255, 255, 0.1);
|
|
overflow: auto;
|
|
position: relative;
|
|
padding: 2rem;
|
|
max-height: 100%;
|
|
width: auto;
|
|
}
|
|
|
|
.pdf-frame::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 4px;
|
|
background: linear-gradient(90deg, #7e22ce 0%, #2a5298 100%);
|
|
z-index: 1;
|
|
}
|
|
|
|
.pdf-canvas {
|
|
display: block;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.error-container {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
height: 100%;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.alert {
|
|
border-radius: 12px;
|
|
border: none;
|
|
padding: 2rem;
|
|
max-width: 600px;
|
|
}
|
|
|
|
.alert-danger {
|
|
background: linear-gradient(135deg, #fff1f2 0%, #ffe4e6 100%);
|
|
color: #be123c;
|
|
border-left: 4px solid #e11d48;
|
|
}
|
|
|
|
.alert-warning {
|
|
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
|
color: #92400e;
|
|
border-left: 4px solid #f59e0b;
|
|
}
|
|
|
|
.spinner-border {
|
|
border-width: 0.35rem;
|
|
}
|
|
|
|
@@media (max-width: 768px) {
|
|
.envelope-content {
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
.pdf-frame {
|
|
border-radius: 12px;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.envelope-action-bar {
|
|
padding: 1rem 1.25rem;
|
|
}
|
|
|
|
.envelope-action-bar__inner {
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.envelope-title {
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.envelope-key {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.envelope-logo svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.alert {
|
|
padding: 1.5rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
@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}";
|
|
} else {
|
|
_errorMessage = $"Dokument konnte nicht geladen werden. HTTP Status: {statusCode}";
|
|
}
|
|
} catch (Exception ex) {
|
|
_errorMessage = $"Fehler: {ex.Message}";
|
|
}
|
|
|
|
_isLoading = false;
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender) {
|
|
if (!_pdfLoaded && !string.IsNullOrWhiteSpace(_pdfDataUrl)) {
|
|
await Task.Delay(500);
|
|
|
|
try {
|
|
var success = await JSRuntime.InvokeAsync<bool>("pdfViewer.initialize", "pdf-canvas", _pdfDataUrl);
|
|
|
|
if (success) {
|
|
_pdfLoaded = true;
|
|
_totalPages = await JSRuntime.InvokeAsync<int>("pdfViewer.getTotalPages");
|
|
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
} catch (Exception ex) {
|
|
_errorMessage = $"PDF.js Fehler: {ex.Message}";
|
|
await InvokeAsync(StateHasChanged);
|
|
}
|
|
}
|
|
}
|
|
|
|
async Task NextPage() {
|
|
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.nextPage")) {
|
|
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
|
}
|
|
}
|
|
|
|
async Task PreviousPage() {
|
|
if (await JSRuntime.InvokeAsync<bool>("pdfViewer.previousPage")) {
|
|
_currentPage = await JSRuntime.InvokeAsync<int>("pdfViewer.getCurrentPage");
|
|
}
|
|
}
|
|
|
|
async Task ZoomIn() {
|
|
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
|
|
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
|
_currentZoom = (int)(scale * 100);
|
|
}
|
|
|
|
async Task ZoomOut() {
|
|
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
|
|
var scale = await JSRuntime.InvokeAsync<double>("pdfViewer.getScale");
|
|
_currentZoom = (int)(scale * 100);
|
|
}
|
|
|
|
public async ValueTask DisposeAsync() {
|
|
// Cleanup if needed
|
|
}
|
|
}
|