Merge branch 'feat/migr-DxReportViewer' of http://git.dd:3000/AppStd/EnvelopeGenerator into feat/migr-DxReportViewer
This commit is contained in:
@@ -0,0 +1,354 @@
|
|||||||
|
@page "/envelope/editor"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@using DevExpress.Blazor.PdfViewer
|
||||||
|
@using DevExpress.Blazor.Reporting.Models
|
||||||
|
@using DevExpress.Blazor
|
||||||
|
@using EnvelopeGenerator.Server.Client.Services
|
||||||
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
|
@inject IJSRuntime JSRuntime
|
||||||
|
@inject AppVersionService AppVersion
|
||||||
|
@inject ILogger<EnvelopeSenderEditorPage> Logger
|
||||||
|
|
||||||
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
|
<link href="@AppVersion.GetVersionedUrl("css/envelope-viewer.css")" rel="stylesheet" />
|
||||||
|
<script src="@AppVersion.GetVersionedUrl("js/envelope-editor.js")"></script>
|
||||||
|
|
||||||
|
<div class="envelope-viewer-layout">
|
||||||
|
|
||||||
|
@* ── Action Bar ── *@
|
||||||
|
<div class="envelope-action-bar">
|
||||||
|
<div class="envelope-action-bar__inner"
|
||||||
|
style="flex-direction: row; align-items: center; padding: 0.35rem 1.5rem; gap: 0.75rem;">
|
||||||
|
|
||||||
|
@* Left: Title *@
|
||||||
|
<div style="flex: 1; min-width: 0; display: flex; align-items: center; gap: 0.75rem;">
|
||||||
|
<div style="font-size: 0.9rem; font-weight: 600; color: #1f2937; white-space: nowrap;">
|
||||||
|
Neues Dokument
|
||||||
|
</div>
|
||||||
|
@if (_pdfLoaded)
|
||||||
|
{
|
||||||
|
<span style="font-size: 0.7rem; color: #6b7280;">@_fileName</span>
|
||||||
|
@if (_signatureFields.Count > 0)
|
||||||
|
{
|
||||||
|
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.4rem;
|
||||||
|
background: #ede9fe; border-radius: 0.25rem; color: #6d28d9;
|
||||||
|
font-weight: 500; font-size: 0.7rem; white-space: nowrap;">
|
||||||
|
@_signatureFields.Count Signaturfeld@(_signatureFields.Count != 1 ? "er" : "")
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Right: Buttons *@
|
||||||
|
<div style="display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0;">
|
||||||
|
|
||||||
|
@* PDF Upload *@
|
||||||
|
<label class="pdf-toolbar__btn"
|
||||||
|
title="PDF hochladen"
|
||||||
|
style="cursor: pointer; display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.3rem 0.75rem; font-size: 0.75rem; font-weight: 500;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
|
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
||||||
|
</svg>
|
||||||
|
PDF hochladen
|
||||||
|
<InputFile OnChange="OnPdfFileSelectedAsync"
|
||||||
|
accept=".pdf"
|
||||||
|
style="display: none;" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
@if (_pdfLoaded)
|
||||||
|
{
|
||||||
|
@* Toggle placement mode *@
|
||||||
|
<button class="pdf-toolbar__btn @(_placementMode ? "pdf-toolbar__btn--signature-change-active" : "pdf-toolbar__btn--signature-change")"
|
||||||
|
@onclick="TogglePlacementMode"
|
||||||
|
title="@(_placementMode ? "Platzierungsmodus beenden" : "Signaturfeld hinzufügen")">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10z" />
|
||||||
|
</svg>
|
||||||
|
<span class="pdf-toolbar__btn-text">
|
||||||
|
@(_placementMode ? "Abbrechen" : "+ Signaturfeld")
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
@* Clear all fields *@
|
||||||
|
@if (_signatureFields.Count > 0)
|
||||||
|
{
|
||||||
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--reset"
|
||||||
|
@onclick="ClearAllFields"
|
||||||
|
title="Alle Signaturfelder entfernen">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z" />
|
||||||
|
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1 0-2h3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1h3a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3h11V2h-11v1z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@* Save *@
|
||||||
|
<button class="pdf-toolbar__btn pdf-toolbar__btn--success"
|
||||||
|
@onclick="SaveAsync"
|
||||||
|
title="Speichern"
|
||||||
|
style="background: linear-gradient(135deg,#059669,#047857); color:#fff; border-radius:6px; padding:0.3rem 0.75rem; font-size:0.75rem; font-weight:600; border:none;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
|
<path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||||
|
</svg>
|
||||||
|
Speichern
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* Placement mode hint bar *@
|
||||||
|
@if (_placementMode)
|
||||||
|
{
|
||||||
|
<div style="background: #4F46E5; color: white; font-size: 0.75rem; font-weight: 500;
|
||||||
|
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
||||||
|
📌 Klicken Sie auf die gewünschte Stelle im Dokument, um ein Signaturfeld zu platzieren.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@* ── Content ── *@
|
||||||
|
<div class="envelope-content" style="padding: 0; overflow: hidden;">
|
||||||
|
@if (!_pdfLoaded)
|
||||||
|
{
|
||||||
|
@* Empty state *@
|
||||||
|
<div class="d-flex justify-content-center align-items-center h-100">
|
||||||
|
<div class="text-center" style="max-width: 420px;">
|
||||||
|
<div style="width: 72px; height: 72px; background: rgba(255,255,255,0.15);
|
||||||
|
border-radius: 50%; display: flex; align-items: center;
|
||||||
|
justify-content: center; margin: 0 auto 1.25rem;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="white" 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>
|
||||||
|
<h5 class="text-white mb-2">Kein Dokument geladen</h5>
|
||||||
|
<p class="text-white mb-4" style="opacity: 0.75; font-size: 0.85rem;">
|
||||||
|
Laden Sie eine PDF-Datei hoch, um Signaturfelder zu platzieren.
|
||||||
|
</p>
|
||||||
|
<label class="btn btn-light btn-sm" style="cursor: pointer; font-weight: 500;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="me-1" viewBox="0 0 16 16">
|
||||||
|
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z" />
|
||||||
|
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z" />
|
||||||
|
</svg>
|
||||||
|
PDF hochladen
|
||||||
|
<InputFile OnChange="OnPdfFileSelectedAsync" accept=".pdf" style="display: none;" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else if (_errorMessage is not null)
|
||||||
|
{
|
||||||
|
<div class="error-container">
|
||||||
|
<div class="alert alert-danger shadow-lg m-4">
|
||||||
|
<strong>Fehler:</strong> @_errorMessage
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@* PDF viewer + overlay wrapper *@
|
||||||
|
<div id="pdf-editor-wrapper" class="pdf-editor-wrapper"
|
||||||
|
style="position: relative; width: 100%; height: 100%; overflow: auto;">
|
||||||
|
|
||||||
|
@* DxPdfViewer — zoom fixed to 1.0 for reliable coordinate mapping *@
|
||||||
|
<DxPdfViewer @ref="_pdfViewer"
|
||||||
|
DocumentContent="@_pdfBytes"
|
||||||
|
ZoomLevel="1.0"
|
||||||
|
IsSinglePagePreview="false"
|
||||||
|
CssClass="sender-editor-pdf-viewer" />
|
||||||
|
|
||||||
|
@* Transparent overlay for click capture (active only in placement mode) *@
|
||||||
|
<div id="pdf-editor-overlay"
|
||||||
|
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||||
|
z-index: 20;
|
||||||
|
pointer-events: @(_placementMode ? "auto" : "none");
|
||||||
|
cursor: @(_placementMode ? "crosshair" : "default");"
|
||||||
|
@onclick="OnOverlayClickAsync">
|
||||||
|
|
||||||
|
@* Render placed signature field placeholders *@
|
||||||
|
@foreach (var field in _signatureFields)
|
||||||
|
{
|
||||||
|
var f = field; // capture for lambda
|
||||||
|
<div style="position: absolute;
|
||||||
|
left: @(f.DisplayX.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))px;
|
||||||
|
top: @(f.DisplayY.ToString("F1", System.Globalization.CultureInfo.InvariantCulture))px;
|
||||||
|
width: @(SigDisplayW)px;
|
||||||
|
height: @(SigDisplayH)px;
|
||||||
|
border: 2px dashed #4F46E5;
|
||||||
|
background: rgba(79,70,229,0.10);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: default;"
|
||||||
|
@onclick:stopPropagation="true">
|
||||||
|
<span style="font-size: 0.6rem; font-weight: 700; color: #4F46E5;
|
||||||
|
letter-spacing: 0.05em; text-transform: uppercase;">
|
||||||
|
Unterschrift
|
||||||
|
</span>
|
||||||
|
<span style="font-size: 0.55rem; color: #6d28d9; opacity: 0.8;">
|
||||||
|
S.@f.Page
|
||||||
|
</span>
|
||||||
|
<button @onclick="() => RemoveField(f)"
|
||||||
|
style="position: absolute; top: 2px; right: 4px;
|
||||||
|
background: none; border: none; cursor: pointer;
|
||||||
|
color: #6d28d9; font-size: 0.75rem; line-height: 1;
|
||||||
|
padding: 0; opacity: 0.7;"
|
||||||
|
title="Entfernen">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
// ── Constants ──
|
||||||
|
// Signature field size in PDF points (fixed): 1.77" × 1.96" × 72 pt/inch
|
||||||
|
const double SigWidthPt = 1.77 * 72; // 127.44 pt
|
||||||
|
const double SigHeightPt = 1.96 * 72; // 141.12 pt
|
||||||
|
|
||||||
|
// Display size of the overlay placeholder (pixels at zoom=1.0).
|
||||||
|
// At zoom=1.0, 1 CSS px ≈ 1 pt in the DxPdfViewer render.
|
||||||
|
const double SigDisplayW = SigWidthPt;
|
||||||
|
const double SigDisplayH = SigHeightPt;
|
||||||
|
|
||||||
|
// ── State ──
|
||||||
|
DxPdfViewer? _pdfViewer;
|
||||||
|
byte[]? _pdfBytes;
|
||||||
|
bool _pdfLoaded = false;
|
||||||
|
string _fileName = string.Empty;
|
||||||
|
string? _errorMessage;
|
||||||
|
bool _placementMode = false;
|
||||||
|
List<SignatureFieldDraft> _signatureFields = [];
|
||||||
|
|
||||||
|
// ── PDF upload ──
|
||||||
|
async Task OnPdfFileSelectedAsync(InputFileChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_errorMessage = null;
|
||||||
|
var file = e.File;
|
||||||
|
if (file is null) return;
|
||||||
|
|
||||||
|
if (!file.Name.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_errorMessage = "Bitte wählen Sie eine PDF-Datei aus.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Max 50 MB
|
||||||
|
const long maxBytes = 50 * 1024 * 1024;
|
||||||
|
using var ms = new System.IO.MemoryStream();
|
||||||
|
await file.OpenReadStream(maxBytes).CopyToAsync(ms);
|
||||||
|
_pdfBytes = ms.ToArray();
|
||||||
|
_fileName = file.Name;
|
||||||
|
_pdfLoaded = true;
|
||||||
|
_signatureFields.Clear();
|
||||||
|
_placementMode = false;
|
||||||
|
|
||||||
|
Logger.LogInformation("PDF loaded: {Name} ({Size} bytes)", _fileName, _pdfBytes.Length);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_errorMessage = $"Fehler beim Laden der Datei: {ex.Message}";
|
||||||
|
Logger.LogError(ex, "Failed to load PDF file");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Placement mode ──
|
||||||
|
void TogglePlacementMode() => _placementMode = !_placementMode;
|
||||||
|
|
||||||
|
void ClearAllFields()
|
||||||
|
{
|
||||||
|
_signatureFields.Clear();
|
||||||
|
_placementMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveField(SignatureFieldDraft field) => _signatureFields.Remove(field);
|
||||||
|
|
||||||
|
// ── Overlay click → add signature field ──
|
||||||
|
async Task OnOverlayClickAsync(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (!_placementMode) return;
|
||||||
|
|
||||||
|
// Get overlay container bounds via JS
|
||||||
|
var coords = await JSRuntime.InvokeAsync<OverlayCoords>(
|
||||||
|
"envelopeEditor.getClickCoords", "pdf-editor-overlay",
|
||||||
|
e.ClientX, e.ClientY);
|
||||||
|
|
||||||
|
if (coords is null) return;
|
||||||
|
|
||||||
|
// At zoom=1.0: container pixels ≈ PDF display pixels.
|
||||||
|
// DxPdfViewer renders at 96 dpi by default; PDF points = 72 dpi.
|
||||||
|
// Scale factor: 96/72 = 1.333 → px / 1.333 = pt
|
||||||
|
const double pxToPt = 72.0 / 96.0;
|
||||||
|
|
||||||
|
double xPt = coords.RelX * pxToPt;
|
||||||
|
double yPt = coords.RelY * pxToPt;
|
||||||
|
|
||||||
|
// Active page: DxPdfViewer.ActivePageIndex is 0-based
|
||||||
|
int page = (_pdfViewer?.ActivePageIndex ?? 0) + 1;
|
||||||
|
|
||||||
|
// Display position (px on overlay) — keep in px for CSS
|
||||||
|
double displayX = coords.RelX;
|
||||||
|
double displayY = coords.RelY;
|
||||||
|
|
||||||
|
// Prevent placing outside bounds
|
||||||
|
if (displayX < 0 || displayY < 0) return;
|
||||||
|
if (displayX + SigDisplayW > coords.ContainerW) displayX = coords.ContainerW - SigDisplayW;
|
||||||
|
if (displayY + SigDisplayH > coords.ContainerH) displayY = coords.ContainerH - SigDisplayH;
|
||||||
|
|
||||||
|
var field = new SignatureFieldDraft(xPt, yPt, page, displayX, displayY);
|
||||||
|
_signatureFields.Add(field);
|
||||||
|
|
||||||
|
Logger.LogInformation(
|
||||||
|
"Signature field added: Page={Page} X={X:F1}pt Y={Y:F1}pt",
|
||||||
|
page, xPt, yPt);
|
||||||
|
|
||||||
|
// Exit placement mode after one click (user can re-click button for next)
|
||||||
|
_placementMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Save ──
|
||||||
|
async Task SaveAsync()
|
||||||
|
{
|
||||||
|
if (_signatureFields.Count == 0)
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.log",
|
||||||
|
"[SenderEditor] No signature fields to save.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var f in _signatureFields)
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.log",
|
||||||
|
$"[SenderEditor] Field: Page={f.Page} X={f.XPt:F2}pt ({f.XPt/72:F3}in) Y={f.YPt:F2}pt ({f.YPt/72:F3}in)");
|
||||||
|
}
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync("console.log",
|
||||||
|
$"[SenderEditor] Total fields: {_signatureFields.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
|
{
|
||||||
|
if (!_pdfLoaded || _errorMessage is not null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
await JSRuntime.InvokeVoidAsync(
|
||||||
|
"envelopeEditor.syncOverlayToPage",
|
||||||
|
"pdf-editor-wrapper",
|
||||||
|
"pdf-editor-overlay");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Models ──
|
||||||
|
record SignatureFieldDraft(double XPt, double YPt, int Page, double DisplayX, double DisplayY);
|
||||||
|
|
||||||
|
record OverlayCoords(double RelX, double RelY, double ContainerW, double ContainerH);
|
||||||
|
}
|
||||||
@@ -51,6 +51,33 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pdf-editor-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-editor-pdf-viewer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-editor-pdf-viewer .dxbrv-document-surface {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-editor-pdf-viewer .dxbrv-report-preview-content-flex-item {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sender-editor-pdf-viewer .dxbrv-report-preview-content {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.pdf-viewer-container {
|
.pdf-viewer-container {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
window.envelopeEditor = {
|
||||||
|
_overlaySyncState: {},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns click coordinates relative to the overlay element.
|
||||||
|
* @param {string} overlayId - The id of the overlay div
|
||||||
|
* @param {number} clientX - MouseEventArgs.ClientX from Blazor
|
||||||
|
* @param {number} clientY - MouseEventArgs.ClientY from Blazor
|
||||||
|
* @returns {{ relX, relY, containerW, containerH }}
|
||||||
|
*/
|
||||||
|
getClickCoords: function (overlayId, clientX, clientY) {
|
||||||
|
const el = document.getElementById(overlayId);
|
||||||
|
if (!el) return null;
|
||||||
|
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
relX: clientX - rect.left,
|
||||||
|
relY: clientY - rect.top,
|
||||||
|
containerW: rect.width,
|
||||||
|
containerH: rect.height
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
syncOverlayToPage: function (wrapperId, overlayId) {
|
||||||
|
const wrapper = document.getElementById(wrapperId);
|
||||||
|
const overlay = document.getElementById(overlayId);
|
||||||
|
|
||||||
|
if (!wrapper || !overlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = window.envelopeEditor._overlaySyncState[overlayId];
|
||||||
|
if (existing) {
|
||||||
|
return existing.sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
const findTarget = (currentWrapper) => {
|
||||||
|
const page = currentWrapper.querySelector(".dxbrv-report-preview-content");
|
||||||
|
if (page) {
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentWrapper.querySelector(".dxbrv-report-preview-content-img") ||
|
||||||
|
currentWrapper.querySelector("img.dxbrv-report-preview-content-img") ||
|
||||||
|
currentWrapper.querySelector(".dxbrv-document-surface img");
|
||||||
|
};
|
||||||
|
|
||||||
|
const sync = () => {
|
||||||
|
const currentWrapper = document.getElementById(wrapperId);
|
||||||
|
const currentOverlay = document.getElementById(overlayId);
|
||||||
|
|
||||||
|
if (!currentWrapper || !currentOverlay) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target = findTarget(currentWrapper);
|
||||||
|
if (!target) {
|
||||||
|
currentOverlay.style.left = "0px";
|
||||||
|
currentOverlay.style.top = "0px";
|
||||||
|
currentOverlay.style.width = "0px";
|
||||||
|
currentOverlay.style.height = "0px";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapperRect = currentWrapper.getBoundingClientRect();
|
||||||
|
const targetRect = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
currentOverlay.style.left = `${targetRect.left - wrapperRect.left + currentWrapper.scrollLeft}px`;
|
||||||
|
currentOverlay.style.top = `${targetRect.top - wrapperRect.top + currentWrapper.scrollTop}px`;
|
||||||
|
currentOverlay.style.width = `${targetRect.width}px`;
|
||||||
|
currentOverlay.style.height = `${targetRect.height}px`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const scheduleSync = () => requestAnimationFrame(sync);
|
||||||
|
|
||||||
|
const observer = new MutationObserver(scheduleSync);
|
||||||
|
observer.observe(wrapper, { childList: true, subtree: true, attributes: true });
|
||||||
|
|
||||||
|
wrapper.addEventListener("scroll", scheduleSync, { passive: true });
|
||||||
|
window.addEventListener("resize", scheduleSync);
|
||||||
|
|
||||||
|
window.envelopeEditor._overlaySyncState[overlayId] = {
|
||||||
|
sync,
|
||||||
|
observer
|
||||||
|
};
|
||||||
|
|
||||||
|
sync();
|
||||||
|
setTimeout(sync, 50);
|
||||||
|
setTimeout(sync, 150);
|
||||||
|
setTimeout(sync, 400);
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user