Contract Signing and contract printing
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from fastapi import Depends, HTTPException
|
from fastapi import Depends, HTTPException, File, UploadFile
|
||||||
|
import shutil
|
||||||
|
|
||||||
from ..core.routes import get_crud_router
|
from ..core.routes import get_crud_router
|
||||||
from .routes_draft import draft_router
|
from .routes_draft import draft_router
|
||||||
from .print import print_router
|
from .print import print_router
|
||||||
|
|
||||||
from .models import Contract, ContractDraft, replace_variables_in_value
|
from .models import Contract, ContractDraft, Party, replace_variables_in_value
|
||||||
from .schemas import ContractCreate, ContractRead, ContractUpdate
|
from .schemas import ContractCreate, ContractRead, ContractUpdate
|
||||||
|
|
||||||
from ..entity.models import Entity
|
from ..entity.models import Entity
|
||||||
@@ -72,10 +73,31 @@ async def update(id: str, contract_form: ContractUpdate, user=Depends(get_curren
|
|||||||
|
|
||||||
|
|
||||||
@contract_router.get("/signature/{signature_id}", response_description="")
|
@contract_router.get("/signature/{signature_id}", response_description="")
|
||||||
async def get_signature(signature_id: str) -> ContractRead:
|
async def get_signature(signature_id: str) -> Party:
|
||||||
raise HTTPException(status_code=500, detail="Not implemented")
|
contract = await Contract.find_by_signature_id(signature_id)
|
||||||
|
signature = contract.get_signature(signature_id)
|
||||||
|
return signature
|
||||||
|
|
||||||
|
|
||||||
@contract_router.post("/signature/{signature_id}", response_description="")
|
@contract_router.post("/signature/{signature_id}", response_description="")
|
||||||
async def affix_signature(signature_id: str, signature_form: ContractCreate) -> ContractRead:
|
async def affix_signature(signature_id: str, signature_file: UploadFile = File(...)) -> bool:
|
||||||
raise HTTPException(status_code=500, detail="Not implemented")
|
contract = await Contract.find_by_signature_id(signature_id)
|
||||||
|
|
||||||
|
signature_index = contract.get_signature_index(signature_id)
|
||||||
|
signature = contract.parties[signature_index]
|
||||||
|
|
||||||
|
if signature.signature_affixed:
|
||||||
|
raise HTTPException(status_code=400, detail="Signature already affixed")
|
||||||
|
|
||||||
|
with open("media/signatures/{}.png".format(signature_id), "wb") as buffer:
|
||||||
|
shutil.copyfileobj(signature_file.file, buffer)
|
||||||
|
|
||||||
|
update_query = {"$set": {
|
||||||
|
'parties.{}.signature_affixed'.format(signature_index): True
|
||||||
|
}}
|
||||||
|
signature.signature_affixed = True
|
||||||
|
if contract.is_signed():
|
||||||
|
update_query["$set"]['status'] = 'signed'
|
||||||
|
await contract.update(update_query)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import uuid
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
from typing import List, Literal
|
from typing import List, Literal
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from beanie.operators import ElemMatch
|
||||||
|
|
||||||
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
|
from ..core.models import CrudDocument, RichtextSingleline, RichtextMultiline, DictionaryEntry
|
||||||
from ..entity.models import Entity
|
from ..entity.models import Entity
|
||||||
@@ -13,6 +12,7 @@ from ..entity.models import Entity
|
|||||||
class ContractStatus(str, Enum):
|
class ContractStatus(str, Enum):
|
||||||
published = 'published'
|
published = 'published'
|
||||||
signed = 'signed'
|
signed = 'signed'
|
||||||
|
printed = 'printed'
|
||||||
executed = 'executed'
|
executed = 'executed'
|
||||||
|
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ class Party(BaseModel):
|
|||||||
representative: Entity = None
|
representative: Entity = None
|
||||||
signature_uuid: str
|
signature_uuid: str
|
||||||
signature_affixed: bool = False
|
signature_affixed: bool = False
|
||||||
|
signature_png: str = None
|
||||||
|
|
||||||
|
|
||||||
class ProvisionGenuine(BaseModel):
|
class ProvisionGenuine(BaseModel):
|
||||||
@@ -127,6 +128,27 @@ class Contract(CrudDocument):
|
|||||||
hour=0, minute=0, second=0)
|
hour=0, minute=0, second=0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def find_by_signature_id(cls, signature_id: str):
|
||||||
|
crit = ElemMatch(cls.parties, {"signature_uuid": signature_id})
|
||||||
|
return cls.find_one(crit)
|
||||||
|
|
||||||
|
def get_signature(self, signature_id: str):
|
||||||
|
for p in self.parties:
|
||||||
|
if p.signature_uuid == signature_id:
|
||||||
|
return p
|
||||||
|
|
||||||
|
def get_signature_index(self, signature_id: str):
|
||||||
|
for i, p in enumerate(self.parties):
|
||||||
|
if p.signature_uuid == signature_id:
|
||||||
|
return i
|
||||||
|
|
||||||
|
def is_signed(self):
|
||||||
|
for p in self.parties:
|
||||||
|
if not p.signature_affixed:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def replace_variables_in_value(variables, value: str):
|
def replace_variables_in_value(variables, value: str):
|
||||||
for v in variables:
|
for v in variables:
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException
|
||||||
from fastapi.responses import HTMLResponse, FileResponse
|
from fastapi.responses import HTMLResponse, FileResponse
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
|
||||||
from beanie.operators import ElemMatch
|
|
||||||
|
|
||||||
from weasyprint import HTML, CSS
|
from weasyprint import HTML, CSS
|
||||||
from weasyprint.text.fonts import FontConfiguration
|
from weasyprint.text.fonts import FontConfiguration
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from app.entity.models import Entity
|
from app.entity.models import Entity
|
||||||
from app.template.models import ProvisionTemplate
|
from app.template.models import ProvisionTemplate
|
||||||
from ..models import ContractDraft, Contract, replace_variables_in_value
|
from ..models import ContractDraft, Contract, ContractStatus, replace_variables_in_value
|
||||||
|
|
||||||
|
|
||||||
async def build_model(model):
|
async def build_model(model):
|
||||||
@@ -77,32 +77,55 @@ async def render_css(host, contract):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@print_router.get("/preview/draft/{draftId}", response_class=HTMLResponse)
|
@print_router.get("/preview/draft/{draft_id}", response_class=HTMLResponse)
|
||||||
async def preview_draft(draftId: str) -> str:
|
async def preview_draft(draft_id: str) -> str:
|
||||||
draft = await build_model(await ContractDraft.get(draftId))
|
draft = await build_model(await ContractDraft.get(draft_id))
|
||||||
|
|
||||||
return await render_print('localhost', draft)
|
return await render_print('localhost', draft)
|
||||||
|
|
||||||
|
|
||||||
@print_router.get("/preview/{signature_id}", response_class=HTMLResponse)
|
@print_router.get("/preview/{signature_id}", response_class=HTMLResponse)
|
||||||
async def preview_contract(signature_id: str) -> str:
|
async def preview_contract(signature_id: str) -> str:
|
||||||
crit = ElemMatch(Contract.parties, {"signature_uuid": signature_id})
|
contract = await Contract.find_by_signature_id(signature_id)
|
||||||
contract = await Contract.find_one(crit)
|
for p in contract.parties:
|
||||||
|
if p.signature_affixed:
|
||||||
|
p.signature_png = retrieve_signature_png(f'media/signatures/{p.signature_uuid}.png')
|
||||||
|
|
||||||
return await render_print('localhost', contract)
|
return await render_print('localhost', contract)
|
||||||
|
|
||||||
|
|
||||||
@print_router.get("/pdf", response_class=FileResponse)
|
@print_router.get("/pdf/{contract_id}", response_class=FileResponse)
|
||||||
async def create_pdf() -> str:
|
async def create_pdf(contract_id: str) -> str:
|
||||||
draft = await build_model(await ContractDraft.get("63e92534aafed8b509f229c4"))
|
contract = await Contract.get(contract_id)
|
||||||
|
contract_path = "media/contracts/{}.pdf".format(contract_id)
|
||||||
|
if not os.path.isfile(contract_path):
|
||||||
|
if contract.status != ContractStatus.signed:
|
||||||
|
raise HTTPException(status_code=400, detail="Contract is not in a printable state")
|
||||||
|
|
||||||
|
for p in contract.parties:
|
||||||
|
signature_path = f'media/signatures/{p.signature_uuid}.png'
|
||||||
|
p.signature_png = retrieve_signature_png(signature_path)
|
||||||
|
# os.remove(signature_path)
|
||||||
|
|
||||||
font_config = FontConfiguration()
|
font_config = FontConfiguration()
|
||||||
html = HTML(string=await render_print('nginx', draft))
|
html = HTML(string=await render_print('nginx', contract))
|
||||||
css = CSS(string=await render_css('nginx', draft), font_config=font_config)
|
css = CSS(string=await render_css('nginx', contract), font_config=font_config)
|
||||||
|
|
||||||
html.write_pdf('out.pdf', stylesheets=[css], font_config=font_config)
|
html.write_pdf(contract_path, stylesheets=[css], font_config=font_config)
|
||||||
|
update_query = {"$set": {
|
||||||
|
'status': 'printed'
|
||||||
|
}}
|
||||||
|
await contract.update(update_query)
|
||||||
|
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
"out.pdf",
|
contract_path,
|
||||||
media_type="application/pdf",
|
media_type="application/pdf",
|
||||||
filename=draft.name)
|
filename=contract.name)
|
||||||
|
|
||||||
|
|
||||||
|
def retrieve_signature_png(filepath):
|
||||||
|
with open(filepath, "rb") as f:
|
||||||
|
b_content = f.read()
|
||||||
|
base64_utf8_str = base64.b64encode(b_content).decode('utf-8')
|
||||||
|
ext = filepath.split('.')[-1]
|
||||||
|
return f'data:image/{ext};base64,{base64_utf8_str}'
|
||||||
|
|||||||
@@ -60,7 +60,14 @@
|
|||||||
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
|
<p class="mention">(Signatures précédée de la mention « Lu et approuvé »)</p>
|
||||||
<table class="signatures">
|
<table class="signatures">
|
||||||
<tr>
|
<tr>
|
||||||
{% for party in contract.parties %}<td>{{ party.part|safe }}:</td>{% endfor %}
|
{% for party in contract.parties %}
|
||||||
|
<td>
|
||||||
|
{{ party.part|safe }}:<br/>
|
||||||
|
{% if party.signature_png %}
|
||||||
|
<img src="{{ party.signature_png }}" />
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { fabric } from 'fabric';
|
|
||||||
import { ActivatedRoute, ParamMap } from "@angular/router";
|
import { ActivatedRoute, ParamMap } from "@angular/router";
|
||||||
import { DomSanitizer } from "@angular/platform-browser";
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
|
import { ImageUploaderCrudService } from "@common/crud/crud.service";
|
||||||
|
|
||||||
|
|
||||||
export class BaseContractsComponent {
|
export class BaseContractsComponent {
|
||||||
@@ -24,9 +24,36 @@ export class ContractsNewComponent extends BaseContractsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: '../base-view/templates/card.template.html'
|
template:`
|
||||||
|
<label>Download Link:</label>
|
||||||
|
<div class="input-group mb-12">
|
||||||
|
<span class="input-group-text"><a href="{{ this.contractPrintLink! }}" target="_blank">{{ this.contractPrintLink! }} </a></span>
|
||||||
|
<button type="button" class="btn btn-light" [cdkCopyToClipboard]="this.contractPrintLink!"><i-bs name="text-paragraph"/></button>
|
||||||
|
</div>
|
||||||
|
<base-card
|
||||||
|
[resource_id]="this.resource_id"
|
||||||
|
[resource]="this.resource"
|
||||||
|
[schema]="this.schema">
|
||||||
|
</base-card>
|
||||||
|
`,
|
||||||
})
|
})
|
||||||
export class ContractsCardComponent extends BaseContractsComponent{
|
export class ContractsCardComponent extends BaseContractsComponent{
|
||||||
|
resource_id: string | null = null;
|
||||||
|
contractPrintLink: string | null = null;
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.resource_id === null) {
|
||||||
|
this.route.paramMap.subscribe((params: ParamMap) => {
|
||||||
|
this.resource_id = params.get('id')
|
||||||
|
this.contractPrintLink = `${location.origin}/api/v1/contract/print/pdf/${this.resource_id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -47,7 +74,17 @@ export class ContractsCardComponent extends BaseContractsComponent{
|
|||||||
<span>Signature</span>
|
<span>Signature</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
<signature-drawer></signature-drawer>
|
<ng-container *ngIf="this.affixed">This Contract has already been signed by {{ this.signatory }}</ng-container>
|
||||||
|
<div class="row" *ngIf="!this.affixed">
|
||||||
|
<signature-drawer class="col-7"
|
||||||
|
(signatureDrawn$)="postSignature($event)"></signature-drawer>
|
||||||
|
<div class="col-5">
|
||||||
|
<p>Cette page est à la destination exclusive de <strong>{{ this.signatory }}</strong></p>
|
||||||
|
<p>Si vous n'êtes <strong>pas</strong> {{ this.signatory }}, veuillez <strong>fermer cette page immédiatement</strong> et surpprimer tous les liens en votre possession menant vers celle-ci.</p>
|
||||||
|
<p>En vous maintenant et/ou en interagissant avec cette page, vous enfreignez l'article L.229 du code pénal de l'Etat de San Andreas pour <strong>usurpation d'identité</strong> et vous vous exposez ainsi à une amende de 20 000$ ainsi qu'à des poursuites civiles.</p>
|
||||||
|
<p>Le cabinet Cooper, Hillman & Toshi LLC</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-panel>
|
</ngb-panel>
|
||||||
</ngb-accordion>
|
</ngb-accordion>
|
||||||
@@ -55,18 +92,41 @@ export class ContractsCardComponent extends BaseContractsComponent{
|
|||||||
})
|
})
|
||||||
export class ContractsSignatureComponent implements OnInit {
|
export class ContractsSignatureComponent implements OnInit {
|
||||||
signature_id: string | null = null;
|
signature_id: string | null = null;
|
||||||
|
signature: any = {}
|
||||||
|
signatory = ""
|
||||||
|
|
||||||
|
affixed = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private sanitizer: DomSanitizer
|
private sanitizer: DomSanitizer,
|
||||||
|
private crudService: ImageUploaderCrudService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {this.route.paramMap.subscribe((params: ParamMap) => {
|
ngOnInit() {
|
||||||
this.signature_id = params.get('id')
|
this.route.paramMap.subscribe((params: ParamMap) => {
|
||||||
|
this.signature_id = params.get('id');
|
||||||
|
this.crudService.get('contract/signature', this.signature_id!).subscribe( (response:any) => {
|
||||||
|
this.signature = response;
|
||||||
|
this.affixed = this.signature.signature_affixed;
|
||||||
|
if (this.signature.representative) {
|
||||||
|
this.signatory = this.signature.representative.entity_data.firstname + " " + this.signature.representative.entity_data.lastname;
|
||||||
|
} else {
|
||||||
|
this.signatory = this.signature.entity.entity_data.firstname + " " + this.signature.entity.entity_data.lastname;
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
getPreview() {
|
getPreview() {
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl("/api/v1/contract/print/preview/" + this.signature_id);
|
return this.sanitizer.bypassSecurityTrustResourceUrl("/api/v1/contract/print/preview/" + this.signature_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
postSignature(image: string) {
|
||||||
|
this.crudService.upload('contract/signature', this.signature_id!, image).subscribe((v: any) => {
|
||||||
|
if (v) {
|
||||||
|
this.affixed = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ import { DraftsCardComponent, DraftsListComponent, DraftsNewComponent, DraftsNew
|
|||||||
import { FormlyModule } from "@ngx-formly/core";
|
import { FormlyModule } from "@ngx-formly/core";
|
||||||
import { FormlyBootstrapModule } from "@ngx-formly/bootstrap";
|
import { FormlyBootstrapModule } from "@ngx-formly/bootstrap";
|
||||||
import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type";
|
import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type";
|
||||||
import { CrudService } from "@common/crud/crud.service";
|
import { CrudService, ImageUploaderCrudService } from "@common/crud/crud.service";
|
||||||
|
|
||||||
import { NgbAccordionModule, NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
|
import { NgbAccordionModule, NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { allIcons, NgxBootstrapIconsModule } from "ngx-bootstrap-icons";
|
import { allIcons, NgxBootstrapIconsModule } from "ngx-bootstrap-icons";
|
||||||
import { ContractsCardComponent, ContractsListComponent, ContractsNewComponent, ContractsSignatureComponent } from "../contracts/contracts.component";
|
import { ContractsCardComponent, ContractsListComponent, ContractsNewComponent, ContractsSignatureComponent } from "../contracts/contracts.component";
|
||||||
import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent } from "./signature-drawer/signature-drawer.component";
|
import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent } from "./signature-drawer/signature-drawer.component";
|
||||||
|
import { ClipboardModule } from "@angular/cdk/clipboard";
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -29,6 +30,7 @@ import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent
|
|||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
FormlyBootstrapModule,
|
FormlyBootstrapModule,
|
||||||
|
ClipboardModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
DraftsListComponent,
|
DraftsListComponent,
|
||||||
@@ -43,7 +45,7 @@ import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent
|
|||||||
BlackBlueRangeComponent,
|
BlackBlueRangeComponent,
|
||||||
AlphaRangeComponent
|
AlphaRangeComponent
|
||||||
],
|
],
|
||||||
providers: [CrudService]
|
providers: [CrudService, ImageUploaderCrudService]
|
||||||
})
|
})
|
||||||
export class ContractsModule {
|
export class ContractsModule {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="row align-items-start">
|
<div class="row align-items-start">
|
||||||
<canvas id="signatureCanvas" class="col" width="320" height="320"></canvas>
|
<canvas id="signatureCanvas" class="col-9" width="320" height="320"></canvas>
|
||||||
<div class="col-sm-2">
|
<div class="col-3" style="width: 90px">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<span class="btn btn-light btn-file">
|
<span class="btn btn-light btn-file">
|
||||||
<i-bs name="image-fill"></i-bs><input #imageInput type="file" accept="image/png, image/gif, image/jpeg, image/bmp" (change)="addImage($event)">
|
<i-bs name="image-fill"></i-bs><input #imageInput type="file" accept="image/png, image/gif, image/jpeg, image/bmp" (change)="addImage($event)">
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
<alpha-range (change)="updateAlpha($event)"></alpha-range>
|
<alpha-range (change)="updateAlpha($event)"></alpha-range>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -37,7 +36,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary">Sign!</button>
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="sign()"
|
||||||
|
>Sign!</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { fabric } from 'fabric';
|
|||||||
})
|
})
|
||||||
export class SignatureDrawerComponent implements OnInit
|
export class SignatureDrawerComponent implements OnInit
|
||||||
{
|
{
|
||||||
|
@Output() signatureDrawn$ = new EventEmitter<string>()
|
||||||
|
|
||||||
size = 320;
|
size = 320;
|
||||||
canvas: any;
|
canvas: any;
|
||||||
isEditImage = false;
|
isEditImage = false;
|
||||||
@@ -26,6 +28,14 @@ export class SignatureDrawerComponent implements OnInit
|
|||||||
'selection:updated': function() {self.handleElement()},
|
'selection:updated': function() {self.handleElement()},
|
||||||
'selection:created': function() {self.handleElement()}
|
'selection:created': function() {self.handleElement()}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const image = localStorage.getItem('signature_image');
|
||||||
|
if (image) {
|
||||||
|
fabric.Image.fromURL(image , function(img: any) {
|
||||||
|
self.canvas.add(img);
|
||||||
|
self.canvas.renderAll();
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleDrawing() {
|
toggleDrawing() {
|
||||||
@@ -160,6 +170,15 @@ export class SignatureDrawerComponent implements OnInit
|
|||||||
clear() {
|
clear() {
|
||||||
this.canvas.clear();
|
this.canvas.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sign() {
|
||||||
|
const image = this.canvas.toDataURL({
|
||||||
|
format: 'png',
|
||||||
|
quality: 0.8
|
||||||
|
});
|
||||||
|
localStorage.setItem('signature_image', image);
|
||||||
|
this.signatureDrawn$.next(image)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { ArrayTypeComponent } from "./types/array.type";
|
|||||||
import { ObjectTypeComponent } from "./types/object.type";
|
import { ObjectTypeComponent } from "./types/object.type";
|
||||||
import { DatetimeTypeComponent } from "./types/datetime.type";
|
import { DatetimeTypeComponent } from "./types/datetime.type";
|
||||||
import { DateTypeComponent } from "./types/date.type";
|
import { DateTypeComponent } from "./types/date.type";
|
||||||
import { ApiService, CrudService } from "./crud.service";
|
import { ApiService, CrudService, ImageUploaderCrudService } from "./crud.service";
|
||||||
import { CrudFormlyJsonschemaService } from "./crud-formly-jsonschema.service";
|
import { CrudFormlyJsonschemaService } from "./crud-formly-jsonschema.service";
|
||||||
import { NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
import { NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
||||||
import { allIcons, NgxBootstrapIconsModule } from "ngx-bootstrap-icons";
|
import { allIcons, NgxBootstrapIconsModule } from "ngx-bootstrap-icons";
|
||||||
@@ -49,6 +49,7 @@ import { ClipboardModule } from '@angular/cdk/clipboard';
|
|||||||
JsonschemasService,
|
JsonschemasService,
|
||||||
ApiService,
|
ApiService,
|
||||||
CrudService,
|
CrudService,
|
||||||
|
ImageUploaderCrudService,
|
||||||
CrudFormlyJsonschemaService,
|
CrudFormlyJsonschemaService,
|
||||||
DictionaryService
|
DictionaryService
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -116,3 +116,28 @@ export class CrudService extends ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ImageUploaderCrudService extends CrudService {
|
||||||
|
public upload(resource: string, signature_id: string, image: string) {
|
||||||
|
const formData: FormData = new FormData();
|
||||||
|
formData.append("signature_file", dataURIToBlob(image), signature_id + ".png");
|
||||||
|
|
||||||
|
return this.http.post<{ menu: [{}] }>(
|
||||||
|
`${this.api_root}/${resource.toLowerCase()}/${signature_id}`,
|
||||||
|
formData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function dataURIToBlob(dataURI: string) {
|
||||||
|
const splitDataURI = dataURI.split(',')
|
||||||
|
const byteString = splitDataURI[0].indexOf('base64') >= 0 ? atob(splitDataURI[1]) : decodeURI(splitDataURI[1])
|
||||||
|
const mimeString = splitDataURI[0].split(':')[1].split(';')[0]
|
||||||
|
|
||||||
|
const ia = new Uint8Array(byteString.length)
|
||||||
|
for (let i = 0; i < byteString.length; i++)
|
||||||
|
ia[i] = byteString.charCodeAt(i)
|
||||||
|
|
||||||
|
return new Blob([ia], { type: mimeString })
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user