diff --git a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor
index 1c8c3061..d6965fd0 100644
--- a/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor
+++ b/EnvelopeGenerator.ReceiverUI/Pages/EnvelopeViewer.razor
@@ -28,36 +28,6 @@
ID: @EnvelopeKey
- @if (_pdfLoaded) {
-
-
-
-
@(_currentZoom)%
-
-
-
-
-
Seite @_currentPage / @_totalPages
-
-
-
- }
@@ -88,6 +58,64 @@
} else if (!string.IsNullOrWhiteSpace(_pdfDataUrl)) {
+ @if (_pdfLoaded) {
+
+ }
@@ -184,17 +212,45 @@
}
async Task ZoomIn() {
+ if (_currentZoom >= 300) return;
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomIn");
var scale = await JSRuntime.InvokeAsync
("pdfViewer.getScale");
_currentZoom = (int)(scale * 100);
}
async Task ZoomOut() {
+ if (_currentZoom <= 50) return;
await JSRuntime.InvokeVoidAsync("pdfViewer.zoomOut");
var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale");
_currentZoom = (int)(scale * 100);
}
+ async Task SetZoom(int percentage) {
+ var scale = percentage / 100.0;
+ await JSRuntime.InvokeVoidAsync("pdfViewer.setScale", scale);
+ _currentZoom = percentage;
+ }
+
+ async Task OnZoomSliderChanged(ChangeEventArgs e) {
+ if (int.TryParse(e.Value?.ToString(), out var zoom)) {
+ await SetZoom(zoom);
+ }
+ }
+
+ async Task OnPageInputChanged(ChangeEventArgs e) {
+ if (int.TryParse(e.Value?.ToString(), out var pageNum) && pageNum >= 1 && pageNum <= _totalPages) {
+ if (await JSRuntime.InvokeAsync("pdfViewer.goToPage", pageNum)) {
+ _currentPage = pageNum;
+ }
+ }
+ }
+
+ async Task FitToWidth() {
+ await JSRuntime.InvokeVoidAsync("pdfViewer.fitToWidth");
+ var scale = await JSRuntime.InvokeAsync("pdfViewer.getScale");
+ _currentZoom = (int)(scale * 100);
+ }
+
public async ValueTask DisposeAsync() {
if (_pdfLoaded) {
try {
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/css/envelope-viewer.css b/EnvelopeGenerator.ReceiverUI/wwwroot/css/envelope-viewer.css
index bcdf70e2..017ee9c2 100644
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/css/envelope-viewer.css
+++ b/EnvelopeGenerator.ReceiverUI/wwwroot/css/envelope-viewer.css
@@ -43,20 +43,6 @@
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;
@@ -68,9 +54,188 @@
.pdf-viewer-container {
height: 100%;
display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ padding: 0;
+ gap: 1rem;
+}
+
+.pdf-toolbar {
+ background: rgba(255, 255, 255, 0.98);
+ backdrop-filter: blur(20px);
+ border-radius: 12px;
+ padding: 0.75rem 1.25rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ box-shadow:
+ 0 4px 16px rgba(0, 0, 0, 0.1),
+ 0 0 0 1px rgba(126, 34, 206, 0.1);
+ border: 1px solid rgba(126, 34, 206, 0.15);
+ flex-shrink: 0;
+ max-width: 95%;
+ width: fit-content;
+}
+
+.pdf-toolbar__section {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.pdf-toolbar__zoom-section {
+ gap: 0.75rem;
+}
+
+.pdf-toolbar__divider {
+ width: 1px;
+ height: 32px;
+ background: linear-gradient(180deg, transparent 0%, rgba(126, 34, 206, 0.2) 50%, transparent 100%);
+}
+
+.pdf-toolbar__btn {
+ background: linear-gradient(135deg, rgba(126, 34, 206, 0.05) 0%, rgba(42, 82, 152, 0.05) 100%);
+ border: 1px solid rgba(126, 34, 206, 0.2);
+ border-radius: 8px;
+ padding: 0.5rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ display: flex;
align-items: center;
justify-content: center;
- padding: 0;
+ color: #1e293b;
+ min-width: 34px;
+ min-height: 34px;
+}
+
+.pdf-toolbar__btn:hover:not(:disabled) {
+ background: linear-gradient(135deg, rgba(126, 34, 206, 0.1) 0%, rgba(42, 82, 152, 0.1) 100%);
+ border-color: rgba(126, 34, 206, 0.4);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(126, 34, 206, 0.2);
+}
+
+.pdf-toolbar__btn:active:not(:disabled) {
+ transform: translateY(0);
+ box-shadow: 0 2px 6px rgba(126, 34, 206, 0.15);
+}
+
+.pdf-toolbar__btn:disabled {
+ opacity: 0.4;
+ cursor: not-allowed;
+ background: rgba(0, 0, 0, 0.02);
+ border-color: rgba(0, 0, 0, 0.1);
+}
+
+.pdf-toolbar__btn--preset {
+ padding: 0.5rem 0.875rem;
+ font-size: 0.813rem;
+ font-weight: 600;
+ color: #475569;
+ min-width: auto;
+ white-space: nowrap;
+}
+
+.pdf-toolbar__btn--preset svg {
+ flex-shrink: 0;
+}
+
+.pdf-toolbar__page-input-group {
+ display: flex;
+ align-items: center;
+ gap: 0.375rem;
+ background: white;
+ border: 1px solid rgba(126, 34, 206, 0.2);
+ border-radius: 8px;
+ padding: 0.25rem 0.625rem;
+}
+
+.pdf-toolbar__page-input {
+ width: 48px;
+ border: none;
+ outline: none;
+ text-align: center;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: #1e293b;
+ background: transparent;
+ -moz-appearance: textfield;
+}
+
+.pdf-toolbar__page-input::-webkit-outer-spin-button,
+.pdf-toolbar__page-input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+.pdf-toolbar__page-total {
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: #64748b;
+}
+
+.pdf-toolbar__zoom-slider-container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.pdf-toolbar__zoom-slider {
+ -webkit-appearance: none;
+ width: 140px;
+ height: 6px;
+ border-radius: 3px;
+ background: linear-gradient(90deg,
+ rgba(126, 34, 206, 0.1) 0%,
+ rgba(126, 34, 206, 0.2) 50%,
+ rgba(126, 34, 206, 0.1) 100%);
+ outline: none;
+ cursor: pointer;
+}
+
+.pdf-toolbar__zoom-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%);
+ cursor: pointer;
+ box-shadow: 0 2px 8px rgba(126, 34, 206, 0.3);
+ transition: all 0.2s ease;
+}
+
+.pdf-toolbar__zoom-slider::-webkit-slider-thumb:hover {
+ transform: scale(1.15);
+ box-shadow: 0 4px 12px rgba(126, 34, 206, 0.4);
+}
+
+.pdf-toolbar__zoom-slider::-moz-range-thumb {
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #7e22ce 0%, #2a5298 100%);
+ cursor: pointer;
+ border: none;
+ box-shadow: 0 2px 8px rgba(126, 34, 206, 0.3);
+ transition: all 0.2s ease;
+}
+
+.pdf-toolbar__zoom-slider::-moz-range-thumb:hover {
+ transform: scale(1.15);
+ box-shadow: 0 4px 12px rgba(126, 34, 206, 0.4);
+}
+
+.pdf-toolbar__zoom-label {
+ font-size: 0.75rem;
+ font-weight: 700;
+ color: #7e22ce;
+ letter-spacing: 0.025em;
+ min-width: 45px;
+ text-align: center;
}
.pdf-frame {
@@ -82,7 +247,7 @@
overflow: auto;
position: relative;
padding: 2rem;
- height: calc(100vh - 200px);
+ flex: 1;
width: 90%;
max-width: 1200px;
text-align: center;
@@ -142,10 +307,29 @@
padding: 0.75rem;
}
+ .pdf-toolbar {
+ flex-wrap: wrap;
+ padding: 0.625rem 1rem;
+ gap: 0.625rem;
+ max-width: 98%;
+ }
+
+ .pdf-toolbar__divider {
+ display: none;
+ }
+
+ .pdf-toolbar__zoom-slider {
+ width: 100px;
+ }
+
+ .pdf-toolbar__btn--preset {
+ padding: 0.425rem 0.75rem;
+ font-size: 0.75rem;
+ }
+
.pdf-frame {
border-radius: 12px;
padding: 1rem;
- height: calc(100vh - 180px);
width: 95%;
}
diff --git a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js
index ac0c0b72..364f1d99 100644
--- a/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js
+++ b/EnvelopeGenerator.ReceiverUI/wwwroot/js/pdf-viewer.js
@@ -231,6 +231,31 @@ window.pdfViewer = {
}
},
+ setScale(scale) {
+ if (scale >= 0.5 && scale <= 3.0) {
+ this.scale = scale;
+ this.queueRenderPage(this.pageNum);
+ }
+ },
+
+ async fitToWidth() {
+ const container = this.canvas.closest('.pdf-frame');
+ if (!container || !this.pdfDoc) return;
+
+ try {
+ const page = await this.pdfDoc.getPage(this.pageNum);
+ const viewport = page.getViewport({ scale: 1.0 });
+
+ const containerWidth = container.clientWidth - 80;
+ const optimalScale = containerWidth / viewport.width;
+
+ this.scale = Math.min(Math.max(optimalScale, 0.5), 3.0);
+ this.queueRenderPage(this.pageNum);
+ } catch (error) {
+ console.error('Error fitting to width:', error);
+ }
+ },
+
getCurrentPage() {
return this.pageNum;
},