Refactor and cleanup EnvelopeSender components
Refactored code for improved readability, consistency, and maintainability. Removed the WASM boot loading overlay and its associated styles/scripts. Updated SVG paths to use self-closing tags for HTML compliance. Reformatted conditional blocks, state management, and session data logic. Enhanced error handling, spinner visibility, and empty state displays. Removed redundant comments and unused code. Added `envelope-editor.js` for new functionality. Cleaned up CSS and JavaScript related to the removed WASM overlay. Improved grid layout persistence and signature field logic. Adjusted localization strings for better alignment with German translations.
This commit is contained in:
@@ -30,31 +30,13 @@
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- WASM boot loading overlay — hidden once Blazor circuit connects -->
|
||||
<div id="sf-boot-overlay">
|
||||
<div class="sf-boot-spinner">
|
||||
<div class="sf-boot-ring"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Routes />
|
||||
<script src="_content/DevExpress.Blazor.Resources/js/preload-script.js"></script>
|
||||
<script src="js/typed.umd.js"></script>
|
||||
<script src="js/receiver-signature.js?v=9"></script>
|
||||
<script src="js/signflow-theme.js"></script>
|
||||
<script src="js/envelope-editor.js"></script>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script>
|
||||
function sfRemoveBootOverlay() {
|
||||
var overlay = document.getElementById('sf-boot-overlay');
|
||||
if (overlay) { overlay.style.opacity = '0'; setTimeout(function () { if (overlay) overlay.remove(); }, 250); }
|
||||
}
|
||||
// Remove on every enhanced navigation (covers both WASM and Server render modes).
|
||||
Blazor.addEventListener('enhancedload', sfRemoveBootOverlay);
|
||||
// Also remove when WASM circuit is ready (for non-enhanced pages like /sender)
|
||||
document.addEventListener('blazor-circuit-connected', sfRemoveBootOverlay, { once: true });
|
||||
// Fallback: remove after 4s regardless
|
||||
setTimeout(sfRemoveBootOverlay, 4000);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
@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;">
|
||||
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>
|
||||
}
|
||||
@@ -118,8 +118,8 @@
|
||||
<div class="sender-receivers-list">
|
||||
@foreach (var receiver in _receivers)
|
||||
{
|
||||
<div class="sender-receiver-chip"
|
||||
style="border-left: 3px solid @receiver.Color;">
|
||||
<div class="sender-receiver-chip"
|
||||
style="border-left: 3px solid @receiver.Color;">
|
||||
<div class="sender-receiver-chip__body">
|
||||
<div class="sender-receiver-chip__name">@receiver.FullName</div>
|
||||
<div class="sender-receiver-chip__email">@receiver.Email</div>
|
||||
@@ -130,7 +130,7 @@
|
||||
</div>
|
||||
|
||||
<button class="pdf-toolbar__btn pdf-toolbar__btn--signature-change sender-toolbar-action-btn sender-toolbar-action-btn--compact
|
||||
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
|
||||
@(_pendingReceiverForPlacement?.Id == receiver.Id ? "pdf-toolbar__btn--signature-change-active" : "")"
|
||||
@onclick="() => ActivatePlacementForReceiver(receiver)"
|
||||
style="@(_pendingReceiverForPlacement?.Id == receiver.Id ? $"background:{receiver.Color};color:#fff;border-color:{receiver.Color};" : $"color:{receiver.Color};border-color:{receiver.Color};")"
|
||||
title="Signaturfeld platzieren">
|
||||
@@ -211,7 +211,7 @@
|
||||
@if (_pendingReceiverForPlacement is not null)
|
||||
{
|
||||
<div style="background: @_pendingReceiverForPlacement.Color; color: white; font-size: 0.75rem; font-weight: 500;
|
||||
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
||||
padding: 0.3rem 1.5rem; text-align: center; letter-spacing: 0.01em;">
|
||||
📌 Klicken Sie auf die Stelle im Dokument für <strong>@_pendingReceiverForPlacement.FullName</strong>.
|
||||
<button @onclick="CancelPlacement"
|
||||
style="background:none;border:none;color:white;cursor:pointer;font-size:0.75rem;text-decoration:underline;padding:0;">
|
||||
@@ -229,8 +229,8 @@
|
||||
<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;">
|
||||
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>
|
||||
@@ -316,13 +316,13 @@
|
||||
@if (_receiverEmailSuggestions.Count > 0)
|
||||
{
|
||||
<div class="sender-receiver-popup__suggestions">
|
||||
<DxListBox TData="string"
|
||||
TValue="string"
|
||||
Data="@_receiverEmailSuggestions"
|
||||
Value="@_selectedReceiverEmailSuggestion"
|
||||
ValueChanged="OnReceiverEmailSuggestionCommittedAsync"
|
||||
SelectionMode="ListBoxSelectionMode.Single"
|
||||
CssClass="w-100" />
|
||||
<DxListBox TData="string"
|
||||
TValue="string"
|
||||
Data="@_receiverEmailSuggestions"
|
||||
Value="@_selectedReceiverEmailSuggestion"
|
||||
ValueChanged="OnReceiverEmailSuggestionCommittedAsync"
|
||||
SelectionMode="ListBoxSelectionMode.Single"
|
||||
CssClass="w-100" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -370,9 +370,9 @@
|
||||
{
|
||||
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
|
||||
<div style="flex-shrink:0;width:40px;height:40px;background:#d1fae5;border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#065f46" 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"/>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
@@ -389,10 +389,10 @@
|
||||
{
|
||||
<div style="display:flex;align-items:flex-start;gap:1rem;padding:0.5rem 0;">
|
||||
<div style="flex-shrink:0;width:40px;height:40px;background:#fee2e2;border-radius:50%;
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="#991b1b" 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"/>
|
||||
<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>
|
||||
<div>
|
||||
@@ -413,7 +413,7 @@
|
||||
<button class="btn btn-primary"
|
||||
@onclick="GoToDashboard"
|
||||
style="background:linear-gradient(135deg,#059669,#047857);border:none;border-radius:6px;
|
||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||
padding:0.5rem 1.5rem;font-weight:600;color:#fff;">
|
||||
Zur Übersicht
|
||||
</button>
|
||||
}
|
||||
@@ -436,7 +436,7 @@
|
||||
|
||||
// ── Constants ──
|
||||
// Signature field size in PDF points (fixed): 1.77" × 1.96"
|
||||
const double SigWidthPt = 1.77 * 72; // 127.44 pt
|
||||
const double SigWidthPt = 1.77 * 72; // 127.44 pt
|
||||
const double SigHeightPt = 1.96 * 72; // 141.12 pt
|
||||
|
||||
// CssClass for DxPdfViewer — used by JS to locate page elements
|
||||
@@ -447,8 +447,8 @@
|
||||
|
||||
// ── State ──
|
||||
DxPdfViewer? _pdfViewer;
|
||||
bool _pdfLoaded = false;
|
||||
string _fileName = string.Empty;
|
||||
bool _pdfLoaded = false;
|
||||
string _fileName = string.Empty;
|
||||
string? _errorMessage;
|
||||
byte[]? _pdfBytes; // Current rendered PDF (original + placeholders burned in)
|
||||
byte[]? _originalPdfBytes; // Pristine upload — never modified, used as base for redraw
|
||||
@@ -457,25 +457,25 @@
|
||||
ReceiverDraft? _pendingReceiverForPlacement; // Set when user clicks "Signatur hinzufügen"
|
||||
|
||||
// ── Save state ──
|
||||
bool _isSaving = false;
|
||||
bool _savePopupVisible = false;
|
||||
string? _saveErrorMessage = null;
|
||||
bool _isSaving = false;
|
||||
bool _savePopupVisible = false;
|
||||
string? _saveErrorMessage = null;
|
||||
|
||||
// ── Envelope metadata ──
|
||||
string _envelopeTitle = string.Empty;
|
||||
string _envelopeTitle = string.Empty;
|
||||
string _envelopeMessage = string.Empty;
|
||||
bool _titleTouched = false;
|
||||
bool _titleTouched = false;
|
||||
|
||||
List<ReceiverDraft> _receivers = [];
|
||||
bool _receiverPopupVisible;
|
||||
string _receiverDraftName = string.Empty;
|
||||
string _receiverDraftEmail = string.Empty;
|
||||
bool _receiverPopupVisible;
|
||||
string _receiverDraftName = string.Empty;
|
||||
string _receiverDraftEmail = string.Empty;
|
||||
string _receiverDraftPhoneNumber = string.Empty;
|
||||
string? _selectedReceiverEmailSuggestion;
|
||||
string? _receiverPopupValidationMessage;
|
||||
bool _isReceiverEmailSearchRunning;
|
||||
bool _isReceiverEmailSearchRunning;
|
||||
List<string> _receiverEmailSuggestions = [];
|
||||
int _receiverEmailSearchVersion;
|
||||
int _receiverEmailSearchVersion;
|
||||
|
||||
static readonly System.ComponentModel.DataAnnotations.EmailAddressAttribute ReceiverEmailValidator = new();
|
||||
|
||||
@@ -512,12 +512,12 @@
|
||||
&& cached is not null)
|
||||
{
|
||||
_originalPdfBytes = cached.OriginalPdfBytes;
|
||||
_signatureFields = cached.Fields;
|
||||
_fileName = cached.FileName;
|
||||
_pdfLoaded = _originalPdfBytes is { Length: > 0 };
|
||||
_receivers = cached.Receivers;
|
||||
_envelopeTitle = cached.Title;
|
||||
_envelopeMessage = cached.Message;
|
||||
_signatureFields = cached.Fields;
|
||||
_fileName = cached.FileName;
|
||||
_pdfLoaded = _originalPdfBytes is { Length: > 0 };
|
||||
_receivers = cached.Receivers;
|
||||
_envelopeTitle = cached.Title;
|
||||
_envelopeMessage = cached.Message;
|
||||
|
||||
// Redraw placeholders onto the original PDF
|
||||
if (_pdfLoaded)
|
||||
@@ -545,8 +545,8 @@
|
||||
await file.OpenReadStream(maxBytes).CopyToAsync(ms);
|
||||
|
||||
_originalPdfBytes = ms.ToArray();
|
||||
_fileName = file.Name;
|
||||
_pdfLoaded = true;
|
||||
_fileName = file.Name;
|
||||
_pdfLoaded = true;
|
||||
_signatureFields.Clear();
|
||||
_pendingReceiverForPlacement = null;
|
||||
|
||||
@@ -597,12 +597,12 @@
|
||||
double pageHeightPt;
|
||||
try
|
||||
{
|
||||
using var ms = new System.IO.MemoryStream(_originalPdfBytes);
|
||||
var doc = PdfSharp.Pdf.IO.PdfReader.Open(ms, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Import);
|
||||
using var ms = new System.IO.MemoryStream(_originalPdfBytes);
|
||||
var doc = PdfSharp.Pdf.IO.PdfReader.Open(ms, PdfSharp.Pdf.IO.PdfDocumentOpenMode.Import);
|
||||
int pageIndex = Math.Max(0, Math.Min(coords.PageIndex, doc.PageCount - 1));
|
||||
var page = doc.Pages[pageIndex];
|
||||
pageWidthPt = page.Width.Point;
|
||||
pageHeightPt = page.Height.Point;
|
||||
var page = doc.Pages[pageIndex];
|
||||
pageWidthPt = page.Width.Point;
|
||||
pageHeightPt = page.Height.Point;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -614,17 +614,17 @@
|
||||
double xPt = coords.NormX * pageWidthPt;
|
||||
double yPt = coords.NormY * pageHeightPt;
|
||||
|
||||
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt));
|
||||
xPt = Math.Max(0, Math.Min(xPt, pageWidthPt - SigWidthPt));
|
||||
yPt = Math.Max(0, Math.Min(yPt, pageHeightPt - SigHeightPt));
|
||||
|
||||
int page1Based = coords.PageIndex + 1;
|
||||
|
||||
var field = new SignatureFieldDraft(
|
||||
XPt: xPt,
|
||||
YPt: yPt,
|
||||
Page: page1Based,
|
||||
XPt: xPt,
|
||||
YPt: yPt,
|
||||
Page: page1Based,
|
||||
ReceiverName: _pendingReceiverForPlacement.FullName,
|
||||
Color: _pendingReceiverForPlacement.Color);
|
||||
Color: _pendingReceiverForPlacement.Color);
|
||||
|
||||
_signatureFields.Add(field);
|
||||
_pendingReceiverForPlacement = null;
|
||||
@@ -711,7 +711,7 @@
|
||||
string.Join(", ", receiversWithoutField.Select(r => r.FullName)));
|
||||
}
|
||||
|
||||
_isSaving = true;
|
||||
_isSaving = true;
|
||||
_saveErrorMessage = null;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
@@ -728,16 +728,16 @@
|
||||
var fields = _signatureFields
|
||||
.Where(f => f.ReceiverName == receiver.FullName)
|
||||
.Select(f => new DocReceiverElementCreateDto(
|
||||
X: f.XPt / 72.0,
|
||||
Y: f.YPt / 72.0,
|
||||
X: f.XPt / 72.0,
|
||||
Y: f.YPt / 72.0,
|
||||
Page: f.Page))
|
||||
.ToList();
|
||||
|
||||
return new ReceiverGetOrCreateCommand
|
||||
{
|
||||
EmailAddress = receiver.Email,
|
||||
Salution = receiver.FullName,
|
||||
PhoneNumber = string.IsNullOrWhiteSpace(receiver.PhoneNumber)
|
||||
EmailAddress = receiver.Email,
|
||||
Salution = receiver.FullName,
|
||||
PhoneNumber = string.IsNullOrWhiteSpace(receiver.PhoneNumber)
|
||||
? null
|
||||
: receiver.PhoneNumber,
|
||||
DocReceiverElements = fields,
|
||||
@@ -746,13 +746,13 @@
|
||||
|
||||
var command = new CreateEnvelopeReceiverCommand
|
||||
{
|
||||
Title = _envelopeTitle.Trim(),
|
||||
Message = string.IsNullOrWhiteSpace(_envelopeMessage)
|
||||
Title = _envelopeTitle.Trim(),
|
||||
Message = string.IsNullOrWhiteSpace(_envelopeMessage)
|
||||
? "Bitte unterzeichnen Sie das beigefügte Dokument."
|
||||
: _envelopeMessage.Trim(),
|
||||
TFAEnabled = false,
|
||||
Document = new DocumentCreateCommand { DataAsBase64 = docBase64 },
|
||||
Receivers = receiversCmd,
|
||||
Document = new DocumentCreateCommand { DataAsBase64 = docBase64 },
|
||||
Receivers = receiversCmd,
|
||||
};
|
||||
|
||||
var result = await EnvelopeReceiverService.CreateAsync(command);
|
||||
@@ -784,11 +784,11 @@
|
||||
|
||||
var data = new EditorSessionData(
|
||||
OriginalPdfBytes: _originalPdfBytes ?? [],
|
||||
Fields: [.. _signatureFields],
|
||||
FileName: _fileName,
|
||||
Receivers: [.. _receivers],
|
||||
Title: _envelopeTitle,
|
||||
Message: _envelopeMessage);
|
||||
Fields: [.. _signatureFields],
|
||||
FileName: _fileName,
|
||||
Receivers: [.. _receivers],
|
||||
Title: _envelopeTitle,
|
||||
Message: _envelopeMessage);
|
||||
|
||||
MemoryCache.Set(SessionKey, data, SessionTtl);
|
||||
}
|
||||
@@ -798,7 +798,7 @@
|
||||
{
|
||||
if (fields.Count == 0) return originalPdf;
|
||||
|
||||
using var inputMs = new System.IO.MemoryStream(originalPdf);
|
||||
using var inputMs = new System.IO.MemoryStream(originalPdf);
|
||||
using var outputMs = new System.IO.MemoryStream();
|
||||
|
||||
var document = PdfSharp.Pdf.IO.PdfReader.Open(
|
||||
@@ -819,11 +819,11 @@
|
||||
var nameBrush = new PdfSharp.Drawing.XSolidBrush(HexToXColor(field.Color, alpha: 230));
|
||||
|
||||
var fontLabel = new PdfSharp.Drawing.XFont("Arial", 9, PdfSharp.Drawing.XFontStyleEx.Bold);
|
||||
var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular);
|
||||
var fontName = new PdfSharp.Drawing.XFont("Arial", 7, PdfSharp.Drawing.XFontStyleEx.Regular);
|
||||
|
||||
var fmtCenter = new PdfSharp.Drawing.XStringFormat
|
||||
{
|
||||
Alignment = PdfSharp.Drawing.XStringAlignment.Center,
|
||||
Alignment = PdfSharp.Drawing.XStringAlignment.Center,
|
||||
LineAlignment = PdfSharp.Drawing.XLineAlignment.Center,
|
||||
};
|
||||
|
||||
@@ -861,11 +861,11 @@
|
||||
// ── Receiver popup ──
|
||||
void OpenAddReceiverPopup()
|
||||
{
|
||||
_receiverDraftName = string.Empty;
|
||||
_receiverDraftEmail = string.Empty;
|
||||
_receiverDraftName = string.Empty;
|
||||
_receiverDraftEmail = string.Empty;
|
||||
_receiverDraftPhoneNumber = string.Empty;
|
||||
_selectedReceiverEmailSuggestion = null;
|
||||
_receiverPopupValidationMessage = null;
|
||||
_receiverPopupValidationMessage = null;
|
||||
_receiverEmailSuggestions.Clear();
|
||||
_receiverPopupVisible = true;
|
||||
}
|
||||
@@ -873,9 +873,9 @@
|
||||
void CloseAddReceiverPopup()
|
||||
{
|
||||
_receiverPopupVisible = false;
|
||||
_receiverPopupValidationMessage = null;
|
||||
_receiverPopupValidationMessage = null;
|
||||
_selectedReceiverEmailSuggestion = null;
|
||||
_isReceiverEmailSearchRunning = false;
|
||||
_isReceiverEmailSearchRunning = false;
|
||||
}
|
||||
|
||||
void OnReceiverNameChanged(string? value)
|
||||
@@ -947,7 +947,7 @@
|
||||
{
|
||||
_receiverDraftEmail = value?.Trim() ?? string.Empty;
|
||||
_selectedReceiverEmailSuggestion = _receiverDraftEmail;
|
||||
_receiverPopupValidationMessage = null;
|
||||
_receiverPopupValidationMessage = null;
|
||||
|
||||
var searchVersion = ++_receiverEmailSearchVersion;
|
||||
|
||||
@@ -955,7 +955,7 @@
|
||||
{
|
||||
_receiverEmailSuggestions.Clear();
|
||||
_selectedReceiverEmailSuggestion = null;
|
||||
_isReceiverEmailSearchRunning = false;
|
||||
_isReceiverEmailSearchRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -994,8 +994,8 @@
|
||||
|
||||
Task SaveReceiverAsync()
|
||||
{
|
||||
var fullName = _receiverDraftName.Trim();
|
||||
var email = _receiverDraftEmail.Trim();
|
||||
var fullName = _receiverDraftName.Trim();
|
||||
var email = _receiverDraftEmail.Trim();
|
||||
var phoneNumber = _receiverDraftPhoneNumber.Trim();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fullName))
|
||||
@@ -1034,12 +1034,12 @@
|
||||
record ReceiverDraft(Guid Id, string FullName, string Email, string PhoneNumber, string Color);
|
||||
|
||||
record EditorSessionData(
|
||||
byte[] OriginalPdfBytes,
|
||||
byte[] OriginalPdfBytes,
|
||||
List<SignatureFieldDraft> Fields,
|
||||
string FileName,
|
||||
List<ReceiverDraft> Receivers,
|
||||
string Title,
|
||||
string Message);
|
||||
string FileName,
|
||||
List<ReceiverDraft> Receivers,
|
||||
string Title,
|
||||
string Message);
|
||||
|
||||
// ── Receiver colour palette (cycles when > 8 receivers) ──
|
||||
static readonly string[] ReceiverPalette =
|
||||
|
||||
Reference in New Issue
Block a user