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) { +
+
+ +
+ + / @_totalPages +
+ +
+ +
+ +
+ +
+ +
@(_currentZoom)%
+
+ +
+ +
+ +
+ + +
+
+ }
@@ -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; },