Add drag-and-drop support for PDF field repositioning

Introduced drag-and-drop functionality for fields in the PDF editor:
- Added `SenderPdfFieldMoveRequest` record to encapsulate field movement data.
- Enhanced `SenderPdfImageEditor.razor` with drag-and-drop event handlers (`OnFieldDragStart`, `OnPageDropAsync`) and updated field structure.
- Updated `SenderEditorPage.razor` to handle field movement via the `OnMoveFieldAsync` method.
- Improved CSS styles for draggable fields and added a delete button with hover effects.
- Added `getDropCoordsOnPage` function in `envelope-editor.js` to calculate normalized drop coordinates.

These changes improve the user experience by enabling intuitive field placement and management in the PDF editor.
This commit is contained in:
2026-09-30 09:08:17 +02:00
parent 696cd1b623
commit 0c81d79a9b
5 changed files with 153 additions and 11 deletions

View File

@@ -0,0 +1,7 @@
namespace EnvelopeGenerator.Server.Client.Components.PdfEditor;
public sealed record SenderPdfFieldMoveRequest(
int Index,
int Page,
double XPt,
double YPt);

View File

@@ -2,6 +2,7 @@
@using EnvelopeGenerator.Application.Common.Dto
@using Microsoft.Extensions.Localization
@inject IStringLocalizer<Resource> UiLocalizer
@inject IJSRuntime JSRuntime
<div class="pdf-editor-wrapper sender-image-editor sender-editor-pdf-viewer"
style="cursor: @(IsPlacementMode ? "crosshair" : "default");"
@@ -33,7 +34,13 @@
data-editor-page="@pageNumber"
data-page-width-pt="@widthPt"
data-page-height-pt="@heightPt">
<div class="sender-image-editor__page-canvas">
<div class="sender-image-editor__page-canvas"
data-editor-page-canvas="@pageNumber"
ondragover="event.preventDefault(); if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';"
ondragenter="event.preventDefault();"
@ondrop:preventDefault="true"
@ondrop:stopPropagation="true"
@ondrop="args => OnPageDropAsync(args, pageNumber, widthPt, heightPt)">
@if (!string.IsNullOrWhiteSpace(pagePreview?.ImageDataUrl))
{
<img class="sender-image-editor__page-image"
@@ -48,14 +55,25 @@
@foreach (var field in Fields.Where(f => f.Page == pageNumber))
{
<button type="button"
class="sender-image-editor__field"
style="@GetFieldStyle(field, widthPt, heightPt)"
title="@field.ReceiverName"
@onclick:stopPropagation="true"
@onclick="() => RemoveField.InvokeAsync(field.Index)">
@field.ReceiverName
</button>
<div class="sender-image-editor__field"
style="@GetFieldStyle(field, widthPt, heightPt)"
title="@field.ReceiverName"
draggable="true"
@onmousedown="() => OnFieldDragStart(field.Index)"
ondragstart="event.dataTransfer.setData('text/plain', 'field'); event.dataTransfer.effectAllowed = 'move';"
@onclick:stopPropagation="true">
<span class="sender-image-editor__field-label">@field.ReceiverName</span>
<button type="button"
class="sender-image-editor__field-remove"
title='@L("Delete", "Löschen")'
draggable="false"
@onclick:stopPropagation="true"
@onclick="() => RemoveField.InvokeAsync(field.Index)">
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path d="M4 4L12 12M12 4L4 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
</div>
}
</div>
</div>
@@ -86,6 +104,11 @@
[Parameter]
public EventCallback<int> RemoveField { get; set; }
[Parameter]
public EventCallback<SenderPdfFieldMoveRequest> MoveField { get; set; }
private int? _draggedFieldIndex;
private static string GetFieldStyle(SenderPdfFieldOverlay field, double pageWidthPt, double pageHeightPt)
{
var leftPercent = (field.XPt / pageWidthPt) * 100.0;
@@ -108,6 +131,40 @@
await EditorClick.InvokeAsync(args);
}
private void OnFieldDragStart(int fieldIndex)
{
_draggedFieldIndex = fieldIndex;
}
private async Task OnPageDropAsync(DragEventArgs args, int pageNumber, double pageWidthPt, double pageHeightPt)
{
if (_draggedFieldIndex is null || !MoveField.HasDelegate)
return;
var coords = await JSRuntime.InvokeAsync<DropCoords?>(
"envelopeEditor.getDropCoordsOnPage",
"sender-editor-pdf-viewer",
pageNumber,
args.ClientX,
args.ClientY);
if (coords is null)
return;
const double sigWidthPt = 1.77 * 72;
const double sigHeightPt = 1.96 * 72;
var xPt = coords.NormX * pageWidthPt;
var yPt = coords.NormY * pageHeightPt;
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - sigWidthPt));
yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - sigHeightPt));
await MoveField.InvokeAsync(new SenderPdfFieldMoveRequest(_draggedFieldIndex.Value, pageNumber, xPt, yPt));
_draggedFieldIndex = null;
}
private sealed record DropCoords(double NormX, double NormY);
private string L(string key, string fallback)
{
var value = UiLocalizer[key];