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: `
${localized.rejectionReasonQ}
`,
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: `${localized.sigAgree}
`,
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({
instant: iJSON,
structured: 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;
}