Compare commits
19 Commits
8ad7c37261
...
a367c12551
| Author | SHA1 | Date | |
|---|---|---|---|
| a367c12551 | |||
| 35a328f8dc | |||
| d259a15b4b | |||
| 23e0e5ddbe | |||
| 0bb85c28c1 | |||
| a11d9a0e0e | |||
| b9bb058137 | |||
| 0818d7d9eb | |||
| 9d200800c5 | |||
| 6feb601670 | |||
| 39c12ada45 | |||
| 985ad4dc29 | |||
| 038ac2aed0 | |||
| 5e74de0ce7 | |||
| 0ce7ae9494 | |||
| 7041a4694a | |||
| 75e47d10e3 | |||
| 7f9125b3aa | |||
| fee256a51a |
@@ -24,6 +24,26 @@ public record AnnotationCreateDto
|
|||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Type { get; init; } = null!;
|
public string Type { get; init; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public double? X { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public double? Y { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public double? Width { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public double? Height { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ Imports DigitalData.Core.Abstraction.Application.Repository
|
|||||||
Imports DigitalData.Core.Abstractions
|
Imports DigitalData.Core.Abstractions
|
||||||
Imports DigitalData.Modules.Base
|
Imports DigitalData.Modules.Base
|
||||||
Imports DigitalData.Modules.Logging
|
Imports DigitalData.Modules.Logging
|
||||||
Imports DocumentFormat.OpenXml.Drawing.Diagrams
|
|
||||||
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument.FinalizeDocumentExceptions
|
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument.FinalizeDocumentExceptions
|
||||||
Imports EnvelopeGenerator.Domain.Entities
|
Imports EnvelopeGenerator.Domain.Entities
|
||||||
Imports EnvelopeGenerator.PdfEditor
|
Imports EnvelopeGenerator.PdfEditor
|
||||||
@@ -51,21 +50,72 @@ Namespace Jobs.FinalizeDocument
|
|||||||
Public Function BurnElementAnnotsToPDF(pSourceBuffer As Byte(), elements As List(Of Signature)) As Byte()
|
Public Function BurnElementAnnotsToPDF(pSourceBuffer As Byte(), elements As List(Of Signature)) As Byte()
|
||||||
' Add background
|
' Add background
|
||||||
Using doc As Pdf(Of MemoryStream, MemoryStream) = Pdf.FromMemory(pSourceBuffer)
|
Using doc As Pdf(Of MemoryStream, MemoryStream) = Pdf.FromMemory(pSourceBuffer)
|
||||||
Return doc.Background(elements).ExportStream().ToArray()
|
'TODO: take the length from the largest y
|
||||||
End Using
|
pSourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray()
|
||||||
|
|
||||||
For Each element In elements
|
Dim oResult As GdPictureStatus
|
||||||
|
Using oSourceStream As New MemoryStream(pSourceBuffer)
|
||||||
Dim x = ToInches(element.Left)
|
' Open PDF
|
||||||
Dim y = ToInches(element.Top)
|
oResult = Manager.InitFromStream(oSourceStream)
|
||||||
|
If oResult <> GdPictureStatus.OK Then
|
||||||
For Each annot In element.Annotations
|
Throw New BurnAnnotationException($"Could not open document for burning: [{oResult}]")
|
||||||
If annot.Type = AnnotationType.FormField Then
|
|
||||||
AddFormFieldValue(x, y, 100, 180, element.Page, annot.Value)
|
|
||||||
End If
|
End If
|
||||||
Next
|
|
||||||
Next
|
|
||||||
|
|
||||||
|
' Imported from background (add to configuration)
|
||||||
|
Dim margin As Double = 0.2
|
||||||
|
Dim inchFactor As Double = 72
|
||||||
|
|
||||||
|
' Y offset of form fields
|
||||||
|
Dim keys = {"position", "city", "date"} ' add to configuration
|
||||||
|
Dim unitYOffsets = 0.2
|
||||||
|
Dim yOffsetsOfFF = keys.
|
||||||
|
Select(Function(k, i) New With {Key .Key = k, Key .Value = unitYOffsets * i + 1}).
|
||||||
|
ToDictionary(Function(x) x.Key, Function(x) x.Value)
|
||||||
|
|
||||||
|
'Add annotations
|
||||||
|
For Each element In elements
|
||||||
|
|
||||||
|
Dim frameX = (element.Left - 0.7 - margin)
|
||||||
|
|
||||||
|
Dim frame = element.Annotations.FirstOrDefault(Function(a) a.Name = "frame")
|
||||||
|
Dim frameY = element.Top - 0.5 - margin
|
||||||
|
Dim frameYShift = frame.Y - frameY * inchFactor
|
||||||
|
Dim frameXShift = frame.X - frameX * inchFactor
|
||||||
|
|
||||||
|
For Each annot In element.Annotations
|
||||||
|
Dim yOffsetofFF As Double = If(yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF), yOffsetofFF, 0)
|
||||||
|
Dim y = frameY + yOffsetofFF
|
||||||
|
|
||||||
|
If annot.Type = AnnotationType.FormField Then
|
||||||
|
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value)
|
||||||
|
ElseIf annot.Type = AnnotationType.Image Then
|
||||||
|
AddImageAnnotation(
|
||||||
|
annot.X / inchFactor,
|
||||||
|
If(annot.Name = "signature", (annot.Y - frameYShift) / inchFactor, y),
|
||||||
|
annot.Width / inchFactor,
|
||||||
|
annot.Height / inchFactor,
|
||||||
|
element.Page,
|
||||||
|
annot.Value
|
||||||
|
)
|
||||||
|
ElseIf annot.Type = AnnotationType.Ink Then
|
||||||
|
AddInkAnnotation(element.Page, annot.Value)
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Next
|
||||||
|
|
||||||
|
'Save PDF
|
||||||
|
Using oNewStream As New MemoryStream()
|
||||||
|
oResult = Manager.SaveDocumentToPDF(oNewStream)
|
||||||
|
If oResult <> GdPictureStatus.OK Then
|
||||||
|
Throw New BurnAnnotationException($"Could not save document to stream: [{oResult}]")
|
||||||
|
End If
|
||||||
|
|
||||||
|
Manager.Close()
|
||||||
|
|
||||||
|
Return oNewStream.ToArray()
|
||||||
|
End Using
|
||||||
|
End Using
|
||||||
|
End Using
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Public Function BurnInstantJSONAnnotsToPDF(pSourceBuffer As Byte(), pInstantJSONList As List(Of String)) As Byte()
|
Public Function BurnInstantJSONAnnotsToPDF(pSourceBuffer As Byte(), pInstantJSONList As List(Of String)) As Byte()
|
||||||
@@ -135,6 +185,11 @@ Namespace Jobs.FinalizeDocument
|
|||||||
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
Private Sub AddImageAnnotation(x As Double, y As Double, width As Double, height As Double, page As Integer, base64 As String)
|
||||||
|
Manager.SelectPage(page)
|
||||||
|
Manager.AddEmbeddedImageAnnotFromBase64(base64, x, y, width, height)
|
||||||
|
End Sub
|
||||||
|
|
||||||
Private Sub AddImageAnnotation(pAnnotation As Annotation, pAttachments As Dictionary(Of String, Attachment))
|
Private Sub AddImageAnnotation(pAnnotation As Annotation, pAttachments As Dictionary(Of String, Attachment))
|
||||||
Dim oAttachment = pAttachments.Where(Function(a) a.Key = pAnnotation.imageAttachmentId).
|
Dim oAttachment = pAttachments.Where(Function(a) a.Key = pAnnotation.imageAttachmentId).
|
||||||
SingleOrDefault()
|
SingleOrDefault()
|
||||||
@@ -151,6 +206,23 @@ Namespace Jobs.FinalizeDocument
|
|||||||
Manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.binary, oX, oY, oWidth, oHeight)
|
Manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.binary, oX, oY, oWidth, oHeight)
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
|
Private Sub AddInkAnnotation(page As Integer, value As String)
|
||||||
|
|
||||||
|
Dim ink = JsonConvert.DeserializeObject(Of Ink)(value)
|
||||||
|
|
||||||
|
Dim oSegments = ink.lines.points
|
||||||
|
Dim oColor = ColorTranslator.FromHtml(ink.strokeColor)
|
||||||
|
Manager.SelectPage(page)
|
||||||
|
|
||||||
|
For Each oSegment As List(Of List(Of Single)) In oSegments
|
||||||
|
Dim oPoints = oSegment.
|
||||||
|
Select(AddressOf ToPointF).
|
||||||
|
ToArray()
|
||||||
|
|
||||||
|
Manager.AddFreeHandAnnot(oColor, oPoints)
|
||||||
|
Next
|
||||||
|
End Sub
|
||||||
|
|
||||||
Private Sub AddInkAnnotation(pAnnotation As Annotation)
|
Private Sub AddInkAnnotation(pAnnotation As Annotation)
|
||||||
Dim oSegments = pAnnotation.lines.points
|
Dim oSegments = pAnnotation.lines.points
|
||||||
Dim oColor = ColorTranslator.FromHtml(pAnnotation.strokeColor)
|
Dim oColor = ColorTranslator.FromHtml(pAnnotation.strokeColor)
|
||||||
@@ -166,7 +238,8 @@ Namespace Jobs.FinalizeDocument
|
|||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Private Sub AddFormFieldValue(x As Double, y As Double, width As Double, height As Double, page As Integer, value As String)
|
Private Sub AddFormFieldValue(x As Double, y As Double, width As Double, height As Double, page As Integer, value As String)
|
||||||
Manager.SelectPage(page + 1)
|
Manager.SelectPage(page)
|
||||||
|
|
||||||
' Add the text annotation
|
' Add the text annotation
|
||||||
Dim ant = Manager.AddTextAnnot(x, y, width, height, value)
|
Dim ant = Manager.AddTextAnnot(x, y, width, height, value)
|
||||||
|
|
||||||
@@ -324,6 +397,12 @@ Namespace Jobs.FinalizeDocument
|
|||||||
Public Property strokeColor As String
|
Public Property strokeColor As String
|
||||||
End Class
|
End Class
|
||||||
|
|
||||||
|
Friend Class Ink
|
||||||
|
Public Property lines As Lines
|
||||||
|
|
||||||
|
Public Property strokeColor As String
|
||||||
|
End Class
|
||||||
|
|
||||||
Public Class EGName
|
Public Class EGName
|
||||||
Public Shared ReadOnly NoName As String = Guid.NewGuid().ToString()
|
Public Shared ReadOnly NoName As String = Guid.NewGuid().ToString()
|
||||||
|
|
||||||
@@ -354,4 +433,4 @@ Namespace Jobs.FinalizeDocument
|
|||||||
End Class
|
End Class
|
||||||
#End Region
|
#End Region
|
||||||
End Class
|
End Class
|
||||||
End Namespace
|
End Namespace
|
||||||
@@ -36,6 +36,34 @@ public class ElementAnnotation
|
|||||||
[Column("TYPE", TypeName = "nvarchar(50)")]
|
[Column("TYPE", TypeName = "nvarchar(50)")]
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
[Column("POSITION_X", TypeName = "float")]
|
||||||
|
public double
|
||||||
|
#if NET
|
||||||
|
?
|
||||||
|
#endif
|
||||||
|
X { get; set; }
|
||||||
|
|
||||||
|
[Column("POSITION_Y", TypeName = "float")]
|
||||||
|
public double
|
||||||
|
#if NET
|
||||||
|
?
|
||||||
|
#endif
|
||||||
|
Y { get; set; }
|
||||||
|
|
||||||
|
[Column("WIDTH", TypeName = "float")]
|
||||||
|
public double
|
||||||
|
#if NET
|
||||||
|
?
|
||||||
|
#endif
|
||||||
|
Width { get; set; }
|
||||||
|
|
||||||
|
[Column("HEIGHT", TypeName = "float")]
|
||||||
|
public double
|
||||||
|
#if NET
|
||||||
|
?
|
||||||
|
#endif
|
||||||
|
Height { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||||
public DateTime AddedWhen { get; set; }
|
public DateTime AddedWhen { get; set; }
|
||||||
@@ -52,10 +80,10 @@ public class ElementAnnotation
|
|||||||
ChangedWho { get; set; }
|
ChangedWho { get; set; }
|
||||||
|
|
||||||
[ForeignKey("ElementId")]
|
[ForeignKey("ElementId")]
|
||||||
public virtual Signature
|
public virtual Signature
|
||||||
#if NET
|
#if NET
|
||||||
?
|
?
|
||||||
#endif
|
#endif
|
||||||
Element { get; set; }
|
Element { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
using iText.Kernel.Pdf.Canvas;
|
using iText.Kernel.Pdf.Canvas;
|
||||||
using EnvelopeGenerator.Domain.Interfaces;
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
using iText.Kernel.Colors;
|
using iText.Kernel.Colors;
|
||||||
|
using DigitalData.Core.Abstractions.Interfaces;
|
||||||
|
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -100,7 +102,7 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
});
|
});
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures)
|
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52)
|
||||||
where TSignature : ISignature
|
where TSignature : ISignature
|
||||||
{
|
{
|
||||||
// once per page
|
// once per page
|
||||||
@@ -118,8 +120,8 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
double magin = .2;
|
double magin = .2;
|
||||||
double x = (signature.X - .7 - magin) * inchFactor;
|
double x = (signature.X - .7 - magin) * inchFactor;
|
||||||
double y = (signature.Y - .5 - magin) * inchFactor;
|
double y = (signature.Y - .5 - magin) * inchFactor;
|
||||||
double width = 1.9500000000000002 * inchFactor;
|
double width = widthPx * inchFactor;
|
||||||
double height = 2.52 * inchFactor;
|
double height = heightPx * inchFactor;
|
||||||
|
|
||||||
double bottomLineLength = 2.5;
|
double bottomLineLength = 2.5;
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
<PackageTags>digital data envelope generator web</PackageTags>
|
<PackageTags>digital data envelope generator web</PackageTags>
|
||||||
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
||||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||||
<Version>3.4.1</Version>
|
<Version>3.5.0</Version>
|
||||||
<AssemblyVersion>3.4.1</AssemblyVersion>
|
<AssemblyVersion>3.5.0</AssemblyVersion>
|
||||||
<FileVersion>3.4.1</FileVersion>
|
<FileVersion>3.5.0</FileVersion>
|
||||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -321,29 +321,43 @@ function mapSignature(iJSON) {
|
|||||||
// formFields
|
// formFields
|
||||||
...iJSON.formFieldValues.filter(field => !field.name.includes("label")).map((field) => {
|
...iJSON.formFieldValues.filter(field => !field.name.includes("label")).map((field) => {
|
||||||
const nameParts = field.name.split('#');
|
const nameParts = field.name.split('#');
|
||||||
|
const [x, y, width, height] = iJSON.annotations.find(iAnnot => iAnnot.id === field.name).bbox;
|
||||||
return {
|
return {
|
||||||
elementId: Number(nameParts[2]),
|
elementId: Number(nameParts[2]),
|
||||||
name: nameParts[3],
|
name: nameParts[3],
|
||||||
value: field.value,
|
value: field.value,
|
||||||
type: field.type
|
type: field.type,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// frames
|
// frames
|
||||||
...iJSON.annotations.filter(annot => annot.description === 'FRAME').map((annot) => {
|
...iJSON.annotations.filter(annot => annot.description === 'FRAME').map((annot) => {
|
||||||
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
|
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], iJSON.annotations.filter(
|
||||||
|
field => field.id.includes("signature") && field.pageIndex === annot.pageIndex
|
||||||
|
));
|
||||||
const idPartsOfPre = preElement.id.split('#');
|
const idPartsOfPre = preElement.id.split('#');
|
||||||
|
const [x, y, width, height] = annot.bbox;
|
||||||
return {
|
return {
|
||||||
elementId: Number(idPartsOfPre[2]),
|
elementId: Number(idPartsOfPre[2]),
|
||||||
name: 'frame',
|
name: 'frame',
|
||||||
value: fixBase64(iJSON.attachments[annot.imageAttachmentId]?.binary),
|
value: fixBase64(iJSON.attachments[annot.imageAttachmentId]?.binary),
|
||||||
type: annot.type
|
type: annot.type,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// signatures
|
// signatures
|
||||||
...iJSON.annotations.filter(annot => annot.isSignature).map(annot => {
|
...iJSON.annotations.filter(annot => annot.isSignature).map(annot => {
|
||||||
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
|
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], iJSON.annotations.filter(
|
||||||
|
field => field.id.includes("signature") && field.pageIndex === annot.pageIndex
|
||||||
|
));
|
||||||
const idPartsOfPre = preElement.id.split('#');
|
const idPartsOfPre = preElement.id.split('#');
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
@@ -357,11 +371,16 @@ function mapSignature(iJSON) {
|
|||||||
else
|
else
|
||||||
throw new Error("Signature mapping failed: The data structure from the third-party library is incompatible or missing required fields.");
|
throw new Error("Signature mapping failed: The data structure from the third-party library is incompatible or missing required fields.");
|
||||||
|
|
||||||
|
const [x, y, width, height] = annot.bbox;
|
||||||
return {
|
return {
|
||||||
elementId: Number(idPartsOfPre[2]),
|
elementId: Number(idPartsOfPre[2]),
|
||||||
name: 'signature',
|
name: 'signature',
|
||||||
value,
|
value,
|
||||||
type: annot.type
|
type: annot.type,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function detailedCurrentDate() {
|
|||||||
}).format();
|
}).format();
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNearest(origin, getX, getY, ...dests) {
|
function findNearest(origin, getX, getY, dests) {
|
||||||
const distanceToOrigin = (point) => Math.sqrt((getX(origin) - getX(point))**2 + (getY(origin) - getY(point))**2);
|
const distanceToOrigin = (point) => Math.sqrt((getX(origin) - getX(point))**2 + (getY(origin) - getY(point))**2);
|
||||||
return dests.reduce(
|
return dests.reduce(
|
||||||
(nearest, dest) => {
|
(nearest, dest) => {
|
||||||
|
|||||||
2
EnvelopeGenerator.Web/wwwroot/js/util.min.js
vendored
2
EnvelopeGenerator.Web/wwwroot/js/util.min.js
vendored
@@ -1 +1 @@
|
|||||||
function detailedCurrentDate(){return new Intl.DateTimeFormat("de-DE",{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(n,t,i,...r){const u=r=>Math.sqrt((t(n)-t(r))**2+(i(n)-i(r))**2);return r.reduce((n,t)=>{const i=u(t);return i<n.dist?{dist:i,dest:t}:n},{dist:Infinity,dest:null}).dest}const B64ToBuff=n=>new Uint8Array(Array.from(atob(n),n=>n.charCodeAt(0))).buffer,getLocaleDateString=()=>(new Date).toLocaleDateString("de-DE");
|
function detailedCurrentDate(){return new Intl.DateTimeFormat("de-DE",{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(n,t,i,r){const u=r=>Math.sqrt((t(n)-t(r))**2+(i(n)-i(r))**2);return r.reduce((n,t)=>{const i=u(t);return i<n.dist?{dist:i,dest:t}:n},{dist:Infinity,dest:null}).dest}const B64ToBuff=n=>new Uint8Array(Array.from(atob(n),n=>n.charCodeAt(0))).buffer,getLocaleDateString=()=>(new Date).toLocaleDateString("de-DE");
|
||||||
Reference in New Issue
Block a user