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.
311 lines
11 KiB
JavaScript
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;
|
|
}
|