Signature Widget

This commit is contained in:
2023-03-05 14:43:20 +01:00
parent da634c59ee
commit ac981d18fc
7 changed files with 1078 additions and 56 deletions

View File

@@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core';
import { NgbAccordionModule } from "@ng-bootstrap/ng-bootstrap";
import { fabric } from 'fabric';
export class BaseContractsComponent {
@@ -29,12 +29,12 @@ export class ContractsCardComponent extends BaseContractsComponent{
@Component({
template: `
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-0">
<ngb-accordion #acc="ngbAccordion" activeIds="ngb-panel-1">
<ngb-panel>
<ng-template ngbPanelTitle>
<span>Preview</span>
</ng-template>
<ng-template ngbPanelContent class="overflow-hidden">
<ng-template ngbPanelContent>
<iframe width="100%"
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>
@@ -45,11 +45,11 @@ export class ContractsCardComponent extends BaseContractsComponent{
<span>Signature</span>
</ng-template>
<ng-template ngbPanelContent>
<signature-drawer></signature-drawer>
</ng-template>
</ngb-panel>
</ngb-accordion>
`
})
export class ContractsSignatureComponent {
}
}

View File

@@ -9,8 +9,10 @@ import { FormlyBootstrapModule } from "@ngx-formly/bootstrap";
import { ForeignkeyTypeComponent } from "@common/crud/types/foreignkey.type";
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 {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
import { AlphaRangeComponent, BlackBlueRangeComponent, SignatureDrawerComponent } from "./signature-drawer/signature-drawer.component";
@NgModule({
@@ -19,6 +21,8 @@ import {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
BaseViewModule,
ContractsRoutingModule,
NgbAccordionModule,
NgbCollapseModule,
NgxBootstrapIconsModule.pick(allIcons),
FormlyModule.forRoot({
types: [
{ name: 'foreign-key', component: ForeignkeyTypeComponent }
@@ -34,7 +38,10 @@ import {NgbAccordionModule} from "@ng-bootstrap/ng-bootstrap";
ContractsListComponent,
ContractsNewComponent,
ContractsCardComponent,
ContractsSignatureComponent
ContractsSignatureComponent,
SignatureDrawerComponent,
BlackBlueRangeComponent,
AlphaRangeComponent
],
providers: [CrudService]
})

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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">&nbsp;</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)
}
}