diff --git a/EnvelopeGenerator.Web/wwwroot/js/annotation.js b/EnvelopeGenerator.Web/wwwroot/js/annotation.js index e31ff26e..69b53b63 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/annotation.js +++ b/EnvelopeGenerator.Web/wwwroot/js/annotation.js @@ -1,156 +1,173 @@ class Annotation { - createAnnotations(document) { - const annotations = [] + createAnnotations(document) { + const annotations = [] - document.elements.forEach((element) => { - console.log('Creating annotation for element', element.id) + document.elements.forEach((element) => { + console.log('Creating annotation for element', element.id) - const [annotation, formField] = this.createAnnotationFromElement(element) - annotations.push(annotation) - annotations.push(formField) - }) + const [annotation, formField] = this.createAnnotationFromElement(element) + annotations.push(annotation) + annotations.push(formField) + }) - return annotations - } + return annotations + } - async deleteAnnotations(instance) { - let pageAnnotations = ( - await Promise.all( - Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => - instance.getAnnotations(pageIndex) + async deleteAnnotations(instance) { + let pageAnnotations = ( + await Promise.all( + Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => + instance.getAnnotations(pageIndex) + ) + ) ) - ) - ) - .flatMap((annotations) => - annotations.reduce((acc, annotation) => acc.concat(annotation), []) - ) - .filter( - (annotation) => - !!annotation.isSignature || annotation.description == 'FRAME' - ) - //deleting all Annotations - return await instance.delete(pageAnnotations) - } + .flatMap((annotations) => + annotations.reduce((acc, annotation) => acc.concat(annotation), []) + ) + .filter( + (annotation) => + !!annotation.isSignature || annotation.description == 'FRAME' + ) + //deleting all Annotations + return await instance.delete(pageAnnotations) + } - async validateAnnotations(instance) { - let pageAnnotations = ( - await Promise.all( - Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => - instance.getAnnotations(pageIndex) + async validateAnnotations(instance) { + let pageAnnotations = ( + await Promise.all( + Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => + instance.getAnnotations(pageIndex) + ) + ) ) - ) - ) - .flatMap((annotations) => - annotations.reduce((acc, annotation) => acc.concat(annotation), []) - ) - .map((annotation) => { - console.log(annotation.toJS()) + .flatMap((annotations) => + annotations.reduce((acc, annotation) => acc.concat(annotation), []) + ) + .map((annotation) => { + console.log(annotation.toJS()) + return annotation + }) + + return true + } + + createAnnotationFromElement(element) { + const id = PSPDFKit.generateInstantId() + const width = this.inchToPoint(element.width) + const height = this.inchToPoint(element.height) + const top = this.inchToPoint(element.top) - height / 2 + const left = this.inchToPoint(element.left) - width / 2 + const page = element.page - 1 + const annotation = this.createSignatureAnnotation( + id, + width, + height, + top, + left, + page + ) + + const formField = new PSPDFKit.FormFields.SignatureFormField({ + name: id, + annotationIds: PSPDFKit.Immutable.List([annotation.id]), + }) + + return [annotation, formField] + } + + createSignatureAnnotation(id, width, height, top, left, pageIndex) { + const annotation = new PSPDFKit.Annotations.WidgetAnnotation({ + id: id, + pageIndex: pageIndex, + formFieldName: id, + backgroundColor: PSPDFKit.Color.YELLOW, + blendMode: 'multiply', + boundingBox: new PSPDFKit.Geometry.Rect({ + width, + height, + top, + left, + }), + }) + return annotation - }) + } - return true - } + createImageAnnotation(boundingBox, pageIndex, imageAttachmentId) { + const frameAnnotation = new PSPDFKit.Annotations.ImageAnnotation({ + pageIndex: pageIndex, + isSignature: false, + readOnly: true, + locked: true, + lockedContents: true, + contentType: 'image/png', + imageAttachmentId, + description: 'FRAME', + boundingBox: boundingBox, + }) + return frameAnnotation + } - createAnnotationFromElement(element) { - const id = PSPDFKit.generateInstantId() - const width = this.inchToPoint(element.width) - const height = this.inchToPoint(element.height) - const top = this.inchToPoint(element.top) - height / 2 - const left = this.inchToPoint(element.left) - width / 2 - const page = element.page - 1 - const annotation = this.createSignatureAnnotation( - id, - width, - height, - top, - left, - page - ) + async createAnnotationFrameBlob(receiverName, receiverSignature, width, height) { + const canvas = document.createElement('canvas') + const scale = 4 + const fontSize = 10 - const formField = new PSPDFKit.FormFields.SignatureFormField({ - name: id, - annotationIds: PSPDFKit.Immutable.List([annotation.id]), - }) + console.log(receiverSignature) - return [annotation, formField] - } + //canvas.width = width + //canvas.height = height - createSignatureAnnotation(id, width, height, top, left, pageIndex) { - const annotation = new PSPDFKit.Annotations.WidgetAnnotation({ - id: id, - pageIndex: pageIndex, - formFieldName: id, - backgroundColor: PSPDFKit.Color.YELLOW, - blendMode: 'multiply', - boundingBox: new PSPDFKit.Geometry.Rect({ - width, - height, - top, - left, - }), - }) + canvas.width = width * scale + canvas.height = height * scale - return annotation - } + //canvas.style.width = width * 4 + //canvas.style.height = height * 4 - createImageAnnotation(boundingBox, pageIndex, imageAttachmentId) { - const frameAnnotation = new PSPDFKit.Annotations.ImageAnnotation({ - pageIndex: pageIndex, - isSignature: false, - readOnly: true, - locked: true, - lockedContents: true, - contentType: 'image/png', - imageAttachmentId, - description: 'FRAME', - boundingBox: boundingBox, - }) - return frameAnnotation - } + const ctx = canvas.getContext('2d') + // This supposedly makes the lines and text less blurry + // See: https://stackoverflow.com/questions/8696631/canvas-drawings-like-lines-are-blurry + ctx.translate(0.5, 0.5) - async createAnnotationFrameBlob(receiverName, width, height) { - const canvas = document.createElement('canvas') - canvas.width = width - canvas.height = height + // This also should make the lines and text less blurry + ctx.textRendering = "geometricPrecision" - const ctx = canvas.getContext('2d') + const date = new Date() + const dateString = date.toLocaleDateString('de-DE') - const date = new Date() - const dateString = date.toLocaleDateString('de-DE') + const signatureLength = 100 * scale - const signatureLength = 100 + ctx.beginPath() - ctx.beginPath() + ctx.moveTo(30 * scale, 10 * scale) + ctx.lineTo(signatureLength, 10 * scale) - ctx.moveTo(30, 10) - ctx.lineTo(signatureLength, 10) + ctx.moveTo(30 * scale, 10 * scale) + ctx.arcTo(10 * scale, 10 * scale, 10 * scale, 30 * scale, 20 * scale) - ctx.moveTo(30, 10) - ctx.arcTo(10, 10, 10, 30, 20) + ctx.moveTo(10 * scale, 30 * scale) + ctx.arcTo(10 * scale, 50 * scale, 30 * scale, 50 * scale, 20 * scale) - ctx.moveTo(10, 30) - ctx.arcTo(10, 50, 30, 50, 20) + ctx.moveTo(30 * scale, 50 * scale) + ctx.lineTo(signatureLength, 50 * scale) - ctx.moveTo(30, 50) - ctx.lineTo(signatureLength, 50) + ctx.strokeStyle = 'darkblue' + ctx.stroke() - ctx.strokeStyle = 'darkblue' - ctx.stroke() + ctx.fillStyle = 'black' + ctx.font = `${fontSize * scale}px sans-serif` + ctx.fillText('Signed by', 30 * scale, 10 * scale) + ctx.fillText(receiverName + ', ' + dateString, 15 * scale, 60 * scale) - ctx.fillStyle = 'black' - ctx.font = '10px serif' - ctx.fillText('Signed by', 30, 10) - ctx.fillText(receiverName + ', ' + dateString, 15, 60) + return new Promise((resolve) => { + canvas.toBlob((blob) => { + const url = URL.createObjectURL(blob) + resolve(url) + }) + }) + } - return new Promise((resolve) => { - canvas.toBlob((blob) => { - const url = URL.createObjectURL(blob) - resolve(url) - }) - }) - } - - inchToPoint(inch) { - return inch * 72 - } + inchToPoint(inch) { + return inch * 72 + } } diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.js b/EnvelopeGenerator.Web/wwwroot/js/app.js index 7efd2c2d..5c87d8db 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.js @@ -1,230 +1,236 @@ const ActionType = { - Created: 0, - Saved: 1, - Sent: 2, - EmailSent: 3, - Delivered: 4, - Seen: 5, - Signed: 6, - Rejected: 7, + Created: 0, + Saved: 1, + Sent: 2, + EmailSent: 3, + Delivered: 4, + Seen: 5, + Signed: 6, + Rejected: 7, } class App { - constructor(container, envelopeKey) { - this.container = container - this.envelopeKey = envelopeKey + constructor(container, envelopeKey) { + this.container = container + this.envelopeKey = envelopeKey - // Initialize classes - console.debug('Initializing classes..') - this.UI = new UI() - this.Network = new Network() - this.Annotation = new Annotation() + // Initialize classes + console.debug('Initializing classes..') + this.UI = new UI() + this.Network = new Network() + this.Annotation = new Annotation() - this.Instance = null - this.currentDocument = null - this.currentReceiver = null - } - - // This function will be called in the ShowEnvelope.razor page - // and will trigger loading of the Editor Interface - async init() { - // Load the envelope from the database - console.debug('Loading envelope from database..') - const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey) - const envelopeError = !!envelopeResponse.error - - if (envelopeError) { - return Swal.fire({ - title: 'Fehler', - text: 'Umschlag konnte nicht geladen werden!', - icon: 'error', - }) + this.Instance = null + this.currentDocument = null + this.currentReceiver = null } - this.currentDocument = envelopeResponse.data.envelope.documents[0] - this.currentReceiver = envelopeResponse.data.receiver + // This function will be called in the ShowEnvelope.razor page + // and will trigger loading of the Editor Interface + async init() { + // Load the envelope from the database + console.debug('Loading envelope from database..') + const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey) + const envelopeError = !!envelopeResponse.error - // Load the document from the filestore - console.debug('Loading document from filestore') - const documentResponse = await this.Network.getDocument( - this.envelopeKey, - this.currentDocument.id - ) - const documentError = !!documentResponse.error - - if (documentError) { - console.error(documentResponse.error) - return Swal.fire({ - title: 'Fehler', - text: 'Dokument konnte nicht geladen werden!', - icon: 'error', - }) - } - - const arrayBuffer = documentResponse.data - - // Load PSPDFKit - console.debug('Loading PSPDFKit..') - this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container) - this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this)) - - this.Instance.addEventListener( - 'annotations.load', - this.handleAnnotationsLoad.bind(this) - ) - this.Instance.addEventListener( - 'annotations.change', - this.handleAnnotationsChange.bind(this) - ) - this.Instance.addEventListener( - 'annotations.create', - this.handleAnnotationsCreate.bind(this) - ) - - // Load annotations into PSPDFKit - console.debug('Loading annotations..') - - try { - const annotations = this.Annotation.createAnnotations( - this.currentDocument - ) - const createdAnnotations = await this.Instance.create(annotations) - - await this.Network.openDocument(this.envelopeKey) - } catch (e) { - console.error(e) - } - } - - handleAnnotationsLoad(loadedAnnotations) { - console.log('annotations loaded', 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 - 25 - const top = annotation.boundingBox.top - 25 - const width = 150 - const height = 75 - - const imageUrl = await this.Annotation.createAnnotationFrameBlob( - this.currentReceiver.name, - width, - height - ) - - const request = await fetch(imageUrl) - const blob = await request.blob() - const imageAttachmentId = await this.Instance.createAttachment(blob) - - const frameAnnotation = this.Annotation.createImageAnnotation( - new PSPDFKit.Geometry.Rect({ - left: left, - top: top, - width: width, - height: height, - }), - annotation.pageIndex, - imageAttachmentId - ) - - this.Instance.create(frameAnnotation) - } - } - - async handleClick(eventType) { - let result = false - - switch (eventType) { - case 'RESET': - result = await this.handleReset(null) - - if (result == true) { - Swal.fire({ - title: 'Erfolg', - text: 'Dokument wurde zurückgesetzt', - icon: 'info', - }) - } else { - Swal.fire({ - title: 'Fehler', - text: 'Dokument konnte nicht zurückgesetzt werden!', - icon: 'error', - }) + if (envelopeError) { + return Swal.fire({ + title: 'Fehler', + text: 'Umschlag konnte nicht geladen werden!', + icon: 'error', + }) } - break + this.currentDocument = envelopeResponse.data.envelope.documents[0] + this.currentReceiver = envelopeResponse.data.receiver - case 'FINISH': - result = await this.handleFinish(null) + // Load the document from the filestore + console.debug('Loading document from filestore') + const documentResponse = await this.Network.getDocument( + this.envelopeKey, + this.currentDocument.id + ) + const documentError = !!documentResponse.error - if (result == true) { - // Redirect to success page after saving to database - window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success` - } else { - alert('Fehler beim Abschließen des Dokuments!') + if (documentError) { + console.error(documentResponse.error) + return Swal.fire({ + title: 'Fehler', + text: 'Dokument konnte nicht geladen werden!', + icon: 'error', + }) } - break + const arrayBuffer = documentResponse.data - case 'REJECT': - alert('Dokument abgelent!') - } - } + // Load PSPDFKit + console.debug('Loading PSPDFKit..') + this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container) + this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this)) - async handleFinish(event) { - // Save changes before doing anything - try { - await this.Instance.save() - } catch (e) { - console.error(e) - return false + this.Instance.addEventListener( + 'annotations.load', + this.handleAnnotationsLoad.bind(this) + ) + this.Instance.addEventListener( + 'annotations.change', + this.handleAnnotationsChange.bind(this) + ) + this.Instance.addEventListener( + 'annotations.create', + this.handleAnnotationsCreate.bind(this) + ) + + // Load annotations into PSPDFKit + console.debug('Loading annotations..') + + try { + const annotations = this.Annotation.createAnnotations( + this.currentDocument + ) + const createdAnnotations = await this.Instance.create(annotations) + + await this.Network.openDocument(this.envelopeKey) + } catch (e) { + console.error(e) + } } - // Export annotation data and save to database - try { - const json = await this.Instance.exportInstantJSON() - const postEnvelopeResult = await this.Network.postEnvelope( - this.envelopeKey, - this.currentDocument.id, - JSON.stringify(json) - ) + handleAnnotationsLoad(loadedAnnotations) { + console.log('annotations loaded', loadedAnnotations.toJS()) + } - console.log(postEnvelopeResult) + 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 - 25 + const top = annotation.boundingBox.top - 25 + const width = 150 + const height = 75 + + console.log(createdAnnotations) + + console.log(annotation) + + const imageUrl = await this.Annotation.createAnnotationFrameBlob( + this.currentReceiver.name, + this.currentReceiver.signature, + width, + height + ) + + const request = await fetch(imageUrl) + const blob = await request.blob() + const imageAttachmentId = await this.Instance.createAttachment(blob) + + const frameAnnotation = this.Annotation.createImageAnnotation( + new PSPDFKit.Geometry.Rect({ + left: left, + top: top, + width: width, + height: height, + }), + annotation.pageIndex, + imageAttachmentId + ) + + this.Instance.create(frameAnnotation) + } + } + + async handleClick(eventType) { + let result = false + + switch (eventType) { + case 'RESET': + result = await this.handleReset(null) + + if (result == true) { + Swal.fire({ + title: 'Erfolg', + text: 'Dokument wurde zurückgesetzt', + icon: 'info', + }) + } else { + Swal.fire({ + title: 'Fehler', + text: 'Dokument konnte nicht zurückgesetzt werden!', + icon: 'error', + }) + } + + break + + case 'FINISH': + result = await this.handleFinish(null) + + if (result == true) { + // Redirect to success page after saving to database + window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success` + } else { + alert('Fehler beim Abschließen des Dokuments!') + } + + break + + case 'REJECT': + alert('Dokument abgelent!') + } + } + + async handleFinish(event) { + // Save changes before doing anything + try { + await this.Instance.save() + } catch (e) { + console.error(e) + return false + } + + // Export annotation data and save to database + try { + const json = await this.Instance.exportInstantJSON() + const postEnvelopeResult = await this.Network.postEnvelope( + this.envelopeKey, + this.currentDocument.id, + JSON.stringify(json) + ) + + console.log(postEnvelopeResult) + + if (postEnvelopeResult === false) { + return false + } + } catch (e) { + console.error(e) + return false + } + + return true + } + + async handleReset(event) { + const result = await Swal.fire({ + title: 'Sind sie sicher?', + text: 'Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?', + icon: 'question', + }) + + if (result.isConfirmed) { + const result = this.Annotation.deleteAnnotations(this.Instance) + return true + } + + if (result.isDimissed) { + return true + } - if (postEnvelopeResult === false) { return false - } - } catch (e) { - console.error(e) - return false } - - return true - } - - async handleReset(event) { - const result = await Swal.fire({ - title: 'Sind sie sicher?', - text: 'Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?', - icon: 'question', - }) - - if (result.isConfirmed) { - const result = this.Annotation.deleteAnnotations(this.Instance) - return true - } - - if (result.isDimissed) { - return true - } - - return false - } }