UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

379 lines (340 loc) 18.1 kB
import React, { useRef, useEffect, useState } from 'react'; import WebViewer from '@pdftron/webviewer'; import { storeNotif, truncate } from '../../helpers/dashboard'; import { FormattedMessage, useIntl } from 'react-intl'; import PrepareSignaturesField from '../PDF/PrepareSignatureFields'; import { updateXFDF, getDefaultTool } from '../../helpers/pdf'; const Prepare = (props) => { const { ethAccount, ethAlias, handleWebviewer, file, filename, handleProgress, resubmit, signers, annotations, handleAnnotations, handleAnnotList, updateStoredData} = props; const [instance, setInstance] = useState(null); const [activeSigFieldTool, handleActiveSigFieldTool] = useState(null) const { formatMessage, locale } = useIntl(); const viewer = useRef(null); useEffect(() => { if (file == null) { storeNotif(formatMessage({id: 'NO_FILE_UPLOADED'}), formatMessage({id: 'PDF_NEEDS_TO_BE_UPLOADED_FIRST'}), 'danger') } WebViewer( { licenseKey: 'Buildblock Tech Pte. Ltd.:OEM:EthSign::B+:AMS(20220926):60A5E4AD0457F80A7360B13AC982537860615F858748CDEA9BF51DE6240C48AE4AB4B6F5C7', path: '/webviewer/lib', disabledElements: [ 'header', 'toolsHeader', ] }, viewer.current, ).then((instance) => { if(locale=="zh"){ instance.setLanguage('zh_cn'); } handleWebviewer(instance); if (file != null) { instance.loadDocument(file, { filename: filename.name+filename.ext }) } const { Annotations, Tools, annotManager, docViewer, CoreControls } = instance; instance.openElements(['leftPanel']); annotManager.setCurrentUser(ethAlias? ethAlias:ethAccount) // new code signers.map((signer, index) => { class SigFieldTxtAnnotation extends Annotations.CustomAnnotation { constructor() { super('SigFieldTxt'); // provide the custom XFDF element name const page_idx = docViewer.getCurrentPage(); const rotation = docViewer.getCompleteRotation(page_idx)*-90; this.PageRotation = rotation; // TODO: add index in Sending.js this.Subject = "SignatureField" this.Signer = { address: signer.address, rotation: rotation, alias: signer.alias, avatar: signer.avatar }; this.Index = 0; this.Address = signer.alias? truncate(signer.alias) : truncate(signer.address); this.Signed = false; this.vertices = []; const numVertices = 4; for (let i = 0; i < numVertices; ++i) { this.vertices.push(new CoreControls.Math.Point()); } this.selectionModel = SigFieldSelectionModel; } serialize(element, pageMatrix) { this.setCustomData('vertices', this.vertices); const el = super.serialize(element, pageMatrix); // create an attribute to save the vertices list el.setAttribute('Signer', JSON.stringify(this.Signer)); el.setAttribute('Index', this.Index) el.setAttribute('Address', this.Address) el.setAttribute('Signed', this.Signed) return el; } deserialize(element, pageMatrix) { super.deserialize(element, pageMatrix); this.Signer = JSON.parse(element.getAttribute('Signer')); this.Index = element.getAttribute('Index') this.Address = element.getAttribute('Address') this.Signed = element.getAttribute('Signed') const storedVertices = this.getCustomData('vertices'); this.vertices = storedVertices.map(v => new CoreControls.Math.Point(v.x, v.y)); } draw(ctx, pageMatrix) { this.setStyles(ctx, pageMatrix); ctx.beginPath(); ctx.setLineDash([5,3]); ctx.moveTo(this.vertices[0].x, this.vertices[0].y); ctx.lineTo(this.vertices[1].x, this.vertices[1].y); ctx.lineTo(this.vertices[2].x, this.vertices[2].y); ctx.lineTo(this.vertices[3].x, this.vertices[3].y); ctx.closePath(); ctx.stroke(); ctx.fillStyle = "black"; const zoom = docViewer.getZoom(); const textAnnot = this.Address+" Signs Here" ctx.save() ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.rotate(this.PageRotation/180*Math.PI); if (this.PageRotation == -90 || this.PageRotation == -270){ const textHeight = this.Height/11; ctx.font = `${textHeight}px san-serif`; if(this.PageRotation == -90){ ctx.fillText(textAnnot, -this.vertices[0].y-((this.Height/2)), this.vertices[0].x+(this.Width/2)); }else{ ctx.fillText(textAnnot, this.vertices[0].y+(this.Height/2), -this.vertices[0].x-(this.Width/2)); } } else{ const textHeight = this.Width/11; ctx.font = `${textHeight}px san-serif`; if(this.PageRotation == - 180){ ctx.fillText(textAnnot, -this.vertices[0].x+((this.Width/2)-this.Width), -this.vertices[0].y+(this.Height/2)-this.Height); } else{ ctx.fillText(textAnnot, this.vertices[0].x+(this.Width/2), this.vertices[0].y+this.Height/2); } } ctx.stroke(); } resize(rect) { const annotRect = this.getRect(); const deltaX = rect.x1 - annotRect.x1; const deltaY = rect.y1 - annotRect.y1; this.vertices = this.vertices.map((vertex) => { vertex.translate(deltaX, deltaY); return vertex; }); this.setRect(rect); } } // this is necessary to set the elementName before instantiation SigFieldTxtAnnotation.prototype.elementName = 'SigFieldTxt'; SigFieldTxtAnnotation.SerializationType = Annotations.CustomAnnotation.SerializationTypes.CUSTOM; // register the annotation type so that it can be saved to XFDF files annotManager.registerAnnotationType(SigFieldTxtAnnotation.prototype.elementName, SigFieldTxtAnnotation); class SigFieldTxtCreateTool extends Tools.GenericAnnotationCreateTool { constructor(docViewer) { // TriangleAnnotation is the clßßass (function) for our annotation we defined previously super(docViewer, SigFieldTxtAnnotation); } mouseLeftDown(e) { super.mouseLeftDown(e); if (this.annotation) { const zoom = docViewer.getZoom(); this.annotation.Width=68/zoom; this.annotation.Height=34/zoom; this.annotation.Y = this.annotation.Y - this.annotation.Height; this.annotation.vertices[0].x = this.annotation.X; this.annotation.vertices[0].y = this.annotation.Y; this.annotation.vertices[1].x = this.annotation.X + this.annotation.Width; this.annotation.vertices[1].y = this.annotation.Y; this.annotation.vertices[2].x = this.annotation.X + this.annotation.Width; this.annotation.vertices[2].y = this.annotation.Y + this.annotation.Height; this.annotation.vertices[3].x = this.annotation.X; this.annotation.vertices[3].y = this.annotation.Y + this.annotation.Height; // update the annotation appearance annotManager.redrawAnnotation(this.annotation); } } mouseMove(e) { super.mouseMove(e); if (this.annotation) { const zoom = docViewer.getZoom(); const initialHeight = 34/zoom; const intiialWidth = 68/zoom; this.annotation.Width = this.annotation.Width + intiialWidth; this.annotation.Height = this.annotation.Height + initialHeight this.annotation.Y = this.annotation.Y - initialHeight; this.annotation.vertices[0].x = this.annotation.X; this.annotation.vertices[0].y = this.annotation.Y; this.annotation.vertices[1].x = this.annotation.X + this.annotation.Width; this.annotation.vertices[1].y = this.annotation.Y; this.annotation.vertices[2].x = this.annotation.X + this.annotation.Width; this.annotation.vertices[2].y = this.annotation.Y + this.annotation.Height; this.annotation.vertices[3].x = this.annotation.X; this.annotation.vertices[3].y = this.annotation.Y + this.annotation.Height; SigFieldTxtTool.cursor="none" // update the annotation appearance annotManager.redrawAnnotation(this.annotation); } } }; class SigFieldControlHandle extends Annotations.ControlHandle { constructor(annotation, index) { super(); this.annotation = annotation; this.index = index; } getDimensions(annotation, selectionBox, zoom) { let x = annotation.vertices[this.index].x; let y = annotation.vertices[this.index].y; const width = Annotations.ControlHandle.handleWidth / zoom; const height = Annotations.ControlHandle.handleHeight / zoom; let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxY = -Number.MAX_VALUE; for (let i = 0; i < annotation.vertices.length; ++i) { const vertex = annotation.vertices[i]; minX = Math.min(minX, vertex.x); maxX = Math.max(maxX, vertex.x); minY = Math.min(minY, vertex.y); maxY = Math.max(maxY, vertex.y); } x -= width * 0.5; y -= height * 0.5; return new CoreControls.Math.Rect(x, y, x + width, y + height); } move(annotation, deltaX, deltaY, fromPoint, toPoint) { annotation.vertices[this.index].x += deltaX; annotation.vertices[this.index].y += deltaY; let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxY = -Number.MAX_VALUE; if(this.index==0){ annotation.vertices[1].y += deltaY; annotation.vertices[3].x += deltaX; } else if(this.index==1){ annotation.vertices[0].y += deltaY; annotation.vertices[2].x += deltaX; } else if(this.index==2){ annotation.vertices[3].y += deltaY; annotation.vertices[1].x += deltaX; } else if(this.index==3){ annotation.vertices[2].y += deltaY; annotation.vertices[0].x += deltaX; } for (let i = 0; i < annotation.vertices.length; ++i) { const vertex = annotation.vertices[i]; minX = Math.min(minX, vertex.x); maxX = Math.max(maxX, vertex.x); minY = Math.min(minY, vertex.y); maxY = Math.max(maxY, vertex.y); } annotation.vertices[0].x = minX; annotation.vertices[0].y = minY; annotation.vertices[1].x = maxX; annotation.vertices[1].y = minY; annotation.vertices[2].x = maxX; annotation.vertices[2].y = maxY; annotation.vertices[3].x = minX; annotation.vertices[3].y = maxY; const rect = new Annotations.Rect(minX, minY, maxX, maxY); annotation.setRect(rect); return true; } } class SigFieldSelectionModel extends Annotations.SelectionModel { constructor(annotation, canModify) { super(annotation, canModify); if (canModify) { const controlHandles = this.getControlHandles(); controlHandles.push(new SigFieldControlHandle(annotation, 0)); controlHandles.push(new SigFieldControlHandle(annotation, 1)); controlHandles.push(new SigFieldControlHandle(annotation, 2)); controlHandles.push(new SigFieldControlHandle(annotation, 3)); } } } const SigFieldTxtToolName = 'AnnotationCreateSigFieldTxt' + index; const SigFieldTxtTool = new SigFieldTxtCreateTool(docViewer); SigFieldTxtTool.cursor = "url(/files/sign_here_68.png) 0 68,url(/files/sign_here_32.png) 0 32,crosshair" instance.registerTool({ toolName: SigFieldTxtToolName, toolObject: SigFieldTxtTool, buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' + '<path d="M12 7.77L18.39 18H5.61L12 7.77M12 4L2 20h20L12 4z"/>' + '<path fill="none" d="M0 0h24v24H0V0z"/>' + '</svg>', buttonName: 'SigFieldTxtToolButton', tooltip: 'SigFieldTxt' }, SigFieldTxtAnnotation); instance.setHeaderItems((header) => { header.getHeader('toolbarGroup-Shapes').get('freeHandToolGroupButton').insertBefore({ type: 'toolButton', toolName: SigFieldTxtToolName }); }); }); annotManager.on('annotationChanged', async (annotations, action, { imported }) => { if (imported) { const annotList = instance.annotManager.getAnnotationsList(); handleAnnotList(annotList) return; }else{ const annotList = instance.annotManager.getAnnotationsList(); handleAnnotList(annotList) await updateXFDF(instance, annotList, handleAnnotations, updateStoredData); if(action == 'add'){ const signatures = annotations.filter(annot => annot.Subject == "SignatureField") if(signatures.length>0){ const curTool = docViewer.getToolMode() curTool.cursor = "url(/files/sign_here_68.png) 0 68,url(/files/sign_here_32.png) 0 32,crosshair" } instance.setToolMode(getDefaultTool()); handleActiveSigFieldTool(null) annotManager.selectAnnotations(annotations) } } }); docViewer.on('documentLoaded', () => { instance.annotManager.importAnnotations(annotations) setInstance(instance) }); }); }, []); return ( <> <div className="bg-gray-400 flex-grow flex flex-col"> <div className="mx-auto flex-grow flex flex h-full w-full text-gray-300 text-15"> <div className="flex-grow" ref={viewer}> </div> <PrepareSignaturesField instance={instance} signers={signers} handleActiveSigFieldTool={handleActiveSigFieldTool} activeSigFieldTool={activeSigFieldTool} ethAccount={ethAccount}/> </div> </div> <nav className="box-shadow-footer z-10 sticky bottom-0 bg-gray-40"> <div className="max-w-7xl mx-auto px-2 "> <div className="relative flex items-center justify-between h-16"> <div className="flex w-full"> <div className="flex-1"></div> <div className="flex flex-grow justify-center items-center text-gray-80 font-medium" >{filename && filename.name + filename.ext}</div> <div className="select-none flex flex-1 justify-end"> <button className="focus:outline-none text-white mr-5 w-28 py-2 font-medium bg-gray-300 hover:bg-gray-600 rounded-sm" onClick={() => { handleProgress(resubmit ? 0 : 1) }}> <FormattedMessage id='BACK' /> </button> <button className="select-none focus:outline-none text-white w-28 py-2 font-medium bg-orange-500 hover:bg-orange-600 rounded-sm" onClick={() => { handleProgress(resubmit ? 2 : 3) }}> <FormattedMessage id='NEXT' /> </button> </div> </div> </div> </div> </nav> </> ); } export default Prepare;