TekH 9d6074874f fix(annotation): correctly assign elementId for signature annotations
Previously, signature annotations did not include elementId mapping logic, which caused issues when linking annotations to their corresponding elements. This update adds logic to extract elementId from the nearest signature annotation (similar to frame annotations) to ensure proper association.
2025-10-21 09:25:01 +02:00

311 lines
11 KiB
JavaScript

class App {
constructor(envelopeKey, envelopeReceiver, documentBytes, licenseKey, locale, container) {
this.container = container ?? `#${this.constructor.name.toLowerCase()}`;
this.envelopeKey = envelopeKey
this.pdfKit = null
this.currentDocument = envelopeReceiver.envelope.documents[0]
this.currentReceiver = envelopeReceiver.receiver
this.signatureCount = envelopeReceiver.envelope.documents[0].elements.length;
this.envelopeReceiver = envelopeReceiver;
this.documentBytes = documentBytes;
this.licenseKey = licenseKey;
this.locale = locale;
}
async init() {
// Load PSPDFKit
this.pdfKit = await loadPSPDFKit(this.documentBytes, this.container, this.licenseKey, this.locale)
addToolbarItems(this.pdfKit, this.handleClick.bind(this))
this.pdfKit.addEventListener(
'annotations.load',
this.handleAnnotationsLoad.bind(this)
)
this.pdfKit.addEventListener(
'annotations.change',
this.handleAnnotationsChange.bind(this)
)
this.pdfKit.addEventListener(
'annotations.create',
this.handleAnnotationsCreate.bind(this)
)
this.pdfKit.addEventListener("annotations.willChange", _ => {
Comp.ActPanel.Toggle();
});
// Load annotations into PSPDFKit
try {
let signatures = await createAnnotations(this.currentDocument, this.envelopeReceiver.envelopeId, this.envelopeReceiver.receiverId);
await this.pdfKit.create(signatures);
} catch (e) {
console.error("Error loading annotations:", e);
}
//add click events of external buttons
[...document.getElementsByClassName('btn_refresh')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('RESET')));
[...document.getElementsByClassName('btn_complete')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('FINISH')));
[...document.getElementsByClassName('btn_reject')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('REJECT')));
}
handleAnnotationsLoad(loadedAnnotations) {
loadedAnnotations.toJS()
}
handleAnnotationsChange() { }
async handleAnnotationsCreate(createdAnnotations) {
const annotation = createdAnnotations.toJS()[0]
const isFormField = !!annotation.formFieldName
const isSignature = !!annotation.isSignature
if (isFormField === false && isSignature === true) {
const left = annotation.boundingBox.left - 20
const top = annotation.boundingBox.top - 20
const width = 150
const height = 75
const timestamp = new Date()
const imageUrl = await createAnnotationFrameBlob(
this.envelopeReceiver.name,
this.currentReceiver.signature,
timestamp,
width,
height
)
const request = await fetch(imageUrl)
const blob = await request.blob()
const imageAttachmentId = await this.pdfKit.createAttachment(blob)
const frameAnnotation = createImageAnnotation(
new PSPDFKit.Geometry.Rect({
left: left,
top: top,
width: width,
height: height,
}),
annotation.pageIndex,
imageAttachmentId,
generateId(
this.envelopeReceiver.envelopeId,
this.envelopeReceiver.receiverId,
this.fakeElementId--,
'signed'
)
)
this.pdfKit.create(frameAnnotation)
}
}
async handleClick(eventType) {
let result = false
switch (eventType) {
case 'RESET':
result = await this.handleReset(null)
Comp.SignatureProgress.SignedCount = 0;
if (result.isConfirmed) {
Swal.fire({
title: 'Erfolg',
text: 'Dokument wurde zurückgesetzt',
icon: 'info',
})
}
break;
case 'FINISH':
result = await this.handleFinish(null)
if (result == true) {
// Redirect to success page after saving to database
window.location.href = `/Envelope/${this.envelopeKey}`
}
break;
case 'REJECT':
Swal.fire({
title: localized.rejection,
html: `<div class="text-start fs-6 p-0 m-0">${localized.rejectionReasonQ}</div>`,
icon: "question",
input: "text",
inputAttributes: {
autocapitalize: "off"
},
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: localized.complete,
cancelButtonText: localized.back,
showLoaderOnConfirm: true,
preConfirm: async (reason) => {
try {
var res = await rejectEnvelope(reason);
return res;
} catch (error) {
Swal.showValidationMessage(`
Request failed: ${error}
`);
}
},
allowOutsideClick: () => !Swal.isLoading()
}).then((result) => {
if (!result.isConfirmed)
return;
const res = result.value;
if (res.ok) {
reload()
}
else
Swal.showValidationMessage(`Request failed: ${res.message}`);
});
break;
case 'COPY_URL':
const url = window.location.href.replace(/\/readonly/gi, '');
navigator.clipboard.writeText(url).then(function () {
bsNotify('Kopiert', { alert_type: 'success', delay: 4, icon_name: 'check_circle' });
}).catch(function (err) {
bsNotify('Unerwarteter Fehler', { alert_type: 'danger', delay: 4, icon_name: 'error' });
});
break;
case 'SHARE':
// Show the modal
Comp.ShareBackdrop.show();
break;
case 'LOGOUT':
await logout();
break;
}
}
async handleFinish(event) {
const iJSON = await this.pdfKit.exportInstantJSON()
const iFormFieldValues = iJSON.formFieldValues;
//check required
const iReqFields = iFormFieldValues.filter(f => isFieldRequired(f))
const hasEmptyReq = iReqFields.some(f => (f.value === undefined || f.value === null || f.value === ""))
if (hasEmptyReq) {
Swal.fire({
title: 'Warnung',
text: 'Bitte füllen Sie alle Standortinformationen vollständig aus!',
icon: 'warning',
})
return false;
}
//check city
const city_regex = new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$")
const iCityFields = iFormFieldValues.filter(f => isCityField(f))
for (var f of iCityFields)
if (!IS_MOBILE_DEVICE && !city_regex.test(f.value)) {
Swal.fire({
title: 'Warnung',
text: `Bitte überprüfen Sie die eingegebene Ortsangabe "${f.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,
icon: 'warning',
})
return false;
}
//check # of signature
const validationResult = await this.validateAnnotations(this.signatureCount)
if (validationResult === false) {
Swal.fire({
title: 'Warnung',
text: 'Es wurden nicht alle Signaturfelder ausgefüllt!',
icon: 'warning',
})
return false
}
return Swal.fire({
title: localized.confirmation,
html: `<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,
icon: "question",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: localized.finalize,
cancelButtonText: localized.back
}).then(async (result) => {
if (result.isConfirmed) {
//---
// Save changes before doing anything
try {
await this.pdfKit.save()
} catch (e) {
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false
}
// Export annotation data and save to database
try {
const res = await signEnvelope({
psPfKitInstant: iJSON,
psPfKitStructured: mapSignature(iJSON)
});
if (!res.ok) {
if (res.status === 403) {
Swal.fire({
title: 'Warnung',
text: 'Umschlag ist nicht mehr verfügbar.',
icon: 'warning',
})
return false
}
else {
throw new Error()
}
} else
return true
} catch (e) {
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false
}
}
else
return false;
});
}
async validateAnnotations(totalSignatures) {
const annotations = await getAnnotations(this.pdfKit)
const filtered = annotations
.map(a => a.toJS())
.filter(a => a.isSignature)
return totalSignatures <= filtered.length
}
async handleReset(event) {
const result = Swal.fire({
title: 'Sind sie sicher?',
text: 'Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?',
icon: 'question',
showCancelButton: true
})
if (result.isConfirmed) {
const result = await deleteAnnotations(this.pdfKit)
}
return result
}
fakeElementId = 0;
}