Signature Widget
This commit is contained in:
819
front/app/package-lock.json
generated
819
front/app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,8 @@
|
|||||||
"@ngx-formly/core": "^6.0.0",
|
"@ngx-formly/core": "^6.0.0",
|
||||||
"@popperjs/core": "^2.11.6",
|
"@popperjs/core": "^2.11.6",
|
||||||
"@tinymce/tinymce-angular": "^7.0.0",
|
"@tinymce/tinymce-angular": "^7.0.0",
|
||||||
|
"fabric": "^5.3.0",
|
||||||
|
"@types/fabric": "^5.3.0",
|
||||||
"ngx-bootstrap-icons": "^1.9.1",
|
"ngx-bootstrap-icons": "^1.9.1",
|
||||||
"ngx-wig": "^15.1.4",
|
"ngx-wig": "^15.1.4",
|
||||||
"rxjs": "~7.5.0",
|
"rxjs": "~7.5.0",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { NgbAccordionModule } from "@ng-bootstrap/ng-bootstrap";
|
import { fabric } from 'fabric';
|
||||||
|
|
||||||
|
|
||||||
export class BaseContractsComponent {
|
export class BaseContractsComponent {
|
||||||
@@ -29,12 +29,12 @@ export class ContractsCardComponent extends BaseContractsComponent{
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0">
|
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-1">
|
||||||
<ngb-panel>
|
<ngb-panel>
|
||||||
<ng-template ngbPanelTitle>
|
<ng-template ngbPanelTitle>
|
||||||
<span>Preview</span>
|
<span>Preview</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent class="overflow-hidden">
|
<ng-template ngbPanelContent>
|
||||||
<iframe width="100%"
|
<iframe width="100%"
|
||||||
src="/api/v1/contract/print/"
|
src="/api/v1/contract/print/"
|
||||||
onload='javascript:(function(o){o.style.height=o.contentWindow.document.body.scrollHeight+"px";o.style.width=o.contentWindow.document.body.scrollWidth+"px";}(this));' style="height:200px;width:100%;border:none;overflow:hidden;"></iframe>
|
onload='javascript:(function(o){o.style.height=o.contentWindow.document.body.scrollHeight+"px";o.style.width=o.contentWindow.document.body.scrollWidth+"px";}(this));' style="height:200px;width:100%;border:none;overflow:hidden;"></iframe>
|
||||||
@@ -45,7 +45,7 @@ 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-template>
|
</ng-template>
|
||||||
</ngb-panel>
|
</ngb-panel>
|
||||||
</ngb-accordion>
|
</ngb-accordion>
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ 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 } from "@common/crud/crud.service";
|
||||||
|
|
||||||
|
import { NgbAccordionModule, NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
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 {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
|
import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent } from "./signature-drawer/signature-drawer.component";
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -19,6 +21,8 @@ import {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
|
|||||||
BaseViewModule,
|
BaseViewModule,
|
||||||
ContractsRoutingModule,
|
ContractsRoutingModule,
|
||||||
NgbAccordionModule,
|
NgbAccordionModule,
|
||||||
|
NgbCollapseModule,
|
||||||
|
NgxBootstrapIconsModule.pick(allIcons),
|
||||||
FormlyModule.forRoot({
|
FormlyModule.forRoot({
|
||||||
types: [
|
types: [
|
||||||
{ name: 'foreign-key', component: ForeignkeyTypeComponent }
|
{ name: 'foreign-key', component: ForeignkeyTypeComponent }
|
||||||
@@ -34,7 +38,10 @@ import {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
|
|||||||
ContractsListComponent,
|
ContractsListComponent,
|
||||||
ContractsNewComponent,
|
ContractsNewComponent,
|
||||||
ContractsCardComponent,
|
ContractsCardComponent,
|
||||||
ContractsSignatureComponent
|
ContractsSignatureComponent,
|
||||||
|
SignatureDrawerComponent,
|
||||||
|
BlackBlueRangeComponent,
|
||||||
|
AlphaRangeComponent
|
||||||
],
|
],
|
||||||
providers: [CrudService]
|
providers: [CrudService]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
canvas {
|
||||||
|
border: solid black 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-file {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.btn-file input[type=file] {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
font-size: 100px;
|
||||||
|
text-align: right;
|
||||||
|
filter: alpha(opacity=0);
|
||||||
|
opacity: 0;
|
||||||
|
outline: none;
|
||||||
|
cursor: inherit;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
|
||||||
|
<div class="row align-items-start">
|
||||||
|
<canvas id="signatureCanvas" class="col" width="320" height="320"></canvas>
|
||||||
|
<div class="col-sm-2">
|
||||||
|
<div class="card">
|
||||||
|
<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)">
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div #collapseImage="ngbCollapse" [(ngbCollapse)]="!isEditImage">
|
||||||
|
<color-range [value]="this.currentColor" (change)="updateColor($event)"></color-range>
|
||||||
|
<alpha-range (change)="updateAlpha($event)"></alpha-range>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-light"
|
||||||
|
(click)="this.toggleDrawing();"
|
||||||
|
[ngClass]="{active: this.isDrawing}"
|
||||||
|
><i-bs name="pencil-fill"></i-bs></button>
|
||||||
|
<div #collapseDrawing="ngbCollapse" [(ngbCollapse)]="!isDrawing">
|
||||||
|
<color-range [value]="this.currentColor" (change)="this.updateColor($event)"></color-range>
|
||||||
|
<label for="thickRange" class="form-label">Thickness</label>
|
||||||
|
<input type="range" class="form-range" #thickRange [value]="this.currentThickness" max="100" (input)="updateThickness(thickRange.value)">
|
||||||
|
<input class="form-control" type="text" #thickInput [value]="this.currentThickness" (change)="updateThickness(thickInput.value)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-danger"
|
||||||
|
(click)="delete()"
|
||||||
|
><i-bs name="eraser-fill"></i-bs></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary">Sign!</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-danger"
|
||||||
|
(click)="clear()"
|
||||||
|
>Clear</button>
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import {Component, Output, EventEmitter, OnInit, Input} from "@angular/core";
|
||||||
|
import { fabric } from 'fabric';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'signature-drawer',
|
||||||
|
templateUrl: './signature-drawer.component.html',
|
||||||
|
styleUrls:['./signature-drawer.component.css']
|
||||||
|
})
|
||||||
|
export class SignatureDrawerComponent implements OnInit
|
||||||
|
{
|
||||||
|
size = 320;
|
||||||
|
canvas: any;
|
||||||
|
isEditImage = false;
|
||||||
|
isDrawing = false;
|
||||||
|
canDelete = false;
|
||||||
|
currentColor = "rgba(0,0,0,1)";
|
||||||
|
currentAlpha = 1;
|
||||||
|
currentThickness = 4;
|
||||||
|
|
||||||
|
elements : any[] = [];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.canvas = new fabric.Canvas('signatureCanvas');
|
||||||
|
let self = this;
|
||||||
|
this.canvas.on({
|
||||||
|
'selection:updated': function() {self.handleElement()},
|
||||||
|
'selection:created': function() {self.handleElement()}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDrawing() {
|
||||||
|
if (this.isDrawing) {
|
||||||
|
this.isDrawing = false;
|
||||||
|
this.canvas.isDrawingMode = false;
|
||||||
|
} else {
|
||||||
|
this.canvas.discardActiveObject().renderAll();
|
||||||
|
this.isEditImage = false;
|
||||||
|
this.isDrawing = true;
|
||||||
|
this.canvas.isDrawingMode = true;
|
||||||
|
let brush = this.canvas.freeDrawingBrush;
|
||||||
|
brush.color = this.currentColor;
|
||||||
|
brush.width = this.currentThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
//this.canvas.freeDrawingBrush.width
|
||||||
|
//this.canvas.freeDrawingBrush.shadow.blur = parseInt(this.value, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleElement(){
|
||||||
|
let selectedObjects = this.canvas.getActiveObject();
|
||||||
|
|
||||||
|
if (selectedObjects.hasOwnProperty("filters")) {
|
||||||
|
this.isEditImage = true;
|
||||||
|
} else {
|
||||||
|
this.isEditImage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColor(value: string) {
|
||||||
|
if (typeof value == "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentColor = value;
|
||||||
|
|
||||||
|
for (let o of this.getSelectedObjects()) {
|
||||||
|
this.updateColorFilter(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
let brush = this.canvas.freeDrawingBrush;
|
||||||
|
brush.color = this.currentColor;
|
||||||
|
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAlpha(value: number) {
|
||||||
|
if (typeof value == "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.currentAlpha = value;
|
||||||
|
|
||||||
|
for (let o of this.getSelectedObjects()) {
|
||||||
|
o.opacity = this.currentAlpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas.renderAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateThickness(value: string) {
|
||||||
|
this.currentThickness = +value
|
||||||
|
|
||||||
|
let brush = this.canvas.freeDrawingBrush;
|
||||||
|
brush.width = this.currentThickness;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelectedObjects(): any[] {
|
||||||
|
let obj = this.canvas.getActiveObject();
|
||||||
|
if (obj === undefined || obj === null) {
|
||||||
|
return []
|
||||||
|
} else if (obj.hasOwnProperty("_objects")) {
|
||||||
|
return obj._objects;
|
||||||
|
} else {
|
||||||
|
return [obj];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColorFilter(o: any) {
|
||||||
|
if (o.hasOwnProperty("filters")) {
|
||||||
|
for (let f of o.filters) {
|
||||||
|
if (f.type == "BlendColor") {
|
||||||
|
f.color = this.currentColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.applyFilters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addImage(event: any) {
|
||||||
|
let file = event.target.files[0]
|
||||||
|
let url = URL.createObjectURL(file);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
fabric.Image.fromURL(url, function(img: any) {
|
||||||
|
let scale;
|
||||||
|
if (img.width > img.height) {
|
||||||
|
img.scaleToWidth(self.size, true);
|
||||||
|
scale = self.size / img.width;
|
||||||
|
} else {
|
||||||
|
img.scaleToHeight(self.size, true);
|
||||||
|
scale = self.size / img.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
let resizeFilter = new fabric.Image.filters.Resize(({
|
||||||
|
resizeType: 'sliceHack',
|
||||||
|
scaleX: scale,
|
||||||
|
scaleY: scale
|
||||||
|
}));
|
||||||
|
|
||||||
|
//img.filters.push(resizeFilter);
|
||||||
|
img.filters.push(new fabric.Image.filters.Grayscale());
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
img.filters.push(new fabric.Image.filters.RemoveColor())
|
||||||
|
img.filters.push(new fabric.Image.filters.BlendColor({
|
||||||
|
color: self.currentColor,
|
||||||
|
mode: 'lighten',
|
||||||
|
}));
|
||||||
|
img.applyFilters();
|
||||||
|
self.canvas.add(img);
|
||||||
|
self.canvas.renderAll();
|
||||||
|
|
||||||
|
self.elements.push(img);
|
||||||
|
event.target.value = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.canvas.remove(this.canvas.getActiveObject());
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.canvas.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'color-range',
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<label for="colorRange" class="form-label">Color</label>
|
||||||
|
|
||||||
|
<input type="range" class="form-range" #colorRange [value]="this.currentColorRatio" max="255" (input)="this.updateColor(colorRange.value)">
|
||||||
|
<div class="input-group">
|
||||||
|
<input class="form-control" type="text" #colorInput [value]="this.currentColorRatio" (change)="this.updateColor(colorInput.value)">
|
||||||
|
<span class="input-group-text" #colorShow [style.background-color]="this.value" style="width: 40px"> </span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class BlackBlueRangeComponent implements OnInit
|
||||||
|
{
|
||||||
|
@Output() change = new EventEmitter<string>();
|
||||||
|
|
||||||
|
currentColorRatio = 255;
|
||||||
|
@Input() value = `rgba(0,0,${this.currentColorRatio},1)`;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
let rgbArr = this.value.substring(4, this.value.length-1).replace(/ /g, '').split(',');
|
||||||
|
this.currentColorRatio = +rgbArr[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColor(value: string) {
|
||||||
|
this.currentColorRatio = +value;
|
||||||
|
this.value = `rgb(0,0,${this.currentColorRatio})`;
|
||||||
|
|
||||||
|
this.change.emit(this.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'alpha-range',
|
||||||
|
template: `
|
||||||
|
<label for="alphaRange" class="form-label">Opacity</label>
|
||||||
|
<input type="range" class="form-range" #alphaRange [value]="this.currentAlphaRatio" max="100" (input)="this.updateAlpha(alphaRange.value)">
|
||||||
|
<input class="form-control" type="text" #alphaInput [value]="this.currentAlphaRatio" (change)="this.updateAlpha(alphaInput.value)">
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AlphaRangeComponent implements OnInit
|
||||||
|
{
|
||||||
|
@Output() change = new EventEmitter<number>();
|
||||||
|
|
||||||
|
currentAlphaRatio = 100;
|
||||||
|
|
||||||
|
@Input() value = this.currentAlphaRatio / 100;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.currentAlphaRatio = this.value * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAlpha(value: string) {
|
||||||
|
this.currentAlphaRatio = +value;
|
||||||
|
this.value = this.currentAlphaRatio / 100;
|
||||||
|
|
||||||
|
this.change.emit(this.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user