我使用角形材料将拖放项(签名)拖放到具有pdf-viewer库的客户端上的pdf文件中。一切正常,我将签名放在后端(带有fpdf和fpdi的php)。
问题是,当客户端将签名拖到容器的底部和顶部时,用户需要手动滚动页面,然后放置签名。 我认为默认情况下,角度材质具有自动滚动功能,但不起作用。
signature.component.html:
<div class="signature-block"
cdkDrag
cdkDragBoundary=".signatures-container"
(cdkDragEnded)="onDragEnded($event)"
[cdkDragFreeDragPosition]="signature.position"
[cdkDragDisabled]="!isEditable">
<img src="{{ signature.imageUrl }}" alt="{{ signature.type }} signature" class="signature-img"
[ngStyle]="{'width': signature.size.width + 'px', 'height': signature.size.height + 'px'}">
<span *ngIf="isEditable" class="signature-button delete" (click)="deleteSignatureClicked()">X</span>
</div>
signature.component.ts
import { Component, OnInit, Input, Output, EventEmitter, ElementRef } from '@angular/core';
import { Signature, SignatureEvent } from '@modules/flow/models/signature.model';
@Component({
selector: 'app-signature',
templateUrl: './signature.component.html',
styleUrls: ['./signature.component.scss']
})
export class SignatureComponent implements OnInit {
@Input() signature: Signature;
@Input() isEditable: boolean;
@Output() signatureUpdated = new EventEmitter<Signature>();
@Output() signatureDeleted = new EventEmitter<SignatureEvent>();
constructor(private elementRef: ElementRef) { }
ngOnInit() {
console.log(`Signature created: [${this.signature.id}] ${this.signature.type}`);
}
onDragEnded(event) {
const position = event.source.getFreeDragPosition();
this.signature.position = position;
this.signatureUpdated.emit(this.signature);
console.log(`Signature dragged: [${this.signature.id}] ${this.signature.type} | position: ${this.signature.position.x}, ${this.signature.position.y}`);
}
getPosition(element) {
let x = 0;
let y = 0;
while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
x += element.offsetLeft - element.scrollLeft;
y += element.offsetTop - element.scrollTop;
element = element.offsetParent;
}
return { top: y, left: x };
}
deleteSignatureClicked() {
this.signatureDeleted.emit({
component: this.elementRef,
signature: this.signature
});
console.log(`Signature deleted: [${this.signature.id}] ${this.signature.type}`);
}
}
父component.ts:
<section class="signature-verification">
<!-- -->
<div class="verification-container">
<div class="row">
<div class="col">
<div class="title-container">
<h4 class="title">
{{ isPartnerViewMode ? 'חתימת שותף' : 'הטמעת חתימות' }}
</h4>
</div>
</div>
</div>
<!-- Edit mode -->
<div class="content" *ngIf="!isPartnerViewMode">
<div class="row">
<div class="col-10">
<p class="description">
</p>
</div>
<div class="col-2">
<div class="editor-pager">
<span ngbDropdown placement="bottom-left">
<button class="btn btn-primary-white" ngbDropdownToggle>
הוספת חתימה
</button>
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
<label class="signature-label">בחירת חתימה</label>
<button class="btn btn-primary identification-signet" ngbDropdownToggle (click)="addSignature(SignatureType.Identification)">חותמת לשם זיהוי</button>
<button class="btn btn-special-blue partner-signet" ngbDropdownToggle (click)="addSignature(SignatureType.Partner)">חותמת שותף</button>
</div>
</span>
</div>
<div class="page-tracker">עמוד {{ currentPage }} מתוך {{ totalPages }}</div>
</div>
</div>
</div>
<!-- Partner view only mode -->
<div class="content" *ngIf="isPartnerViewMode">
<div class="row">
<div class="col-7">
<p class="description">
הוראות לשותף.
</p>
</div>
<div class="col-5">
<div class="partner-pager">
<ul class="list-inline">
<li class="list-inline-item">
<button class="btn btn-primary-white" (click)="showPreviousPartnerSignature()">
<app-svg-icon name="angle-up_special"></app-svg-icon>
לחתימה הקודמת
</button>
</li>
<li class="list-inline-item">
<button class="btn btn-primary-white" (click)="showNextPartnerSignature()">
<app-svg-icon name="angle-down_special"></app-svg-icon>
לחתימה הבאה
</button>
</li>
</ul>
<div class="page-tracker">עמוד {{ currentPage }} מתוך {{ totalPages }}</div>
</div>
</div>
</div>
</div>
<!-- -->
<div class="pdf-container" *ngIf="pdfSrc">
<pdf-viewer
src="{{ pdfSrc }}"
(page-rendered)="onPageRendered($event)"
(after-load-complete)="onLoadComplete($event)"
[(page)]="currentPage"
[render-text]="true"
[original-size]="true"
[show-all]="true"
[show-borders]="false"></pdf-viewer>
</div>
</div>
<!-- -->
</section>
父component.ts
import { Component, Input, OnInit, OnChanges, ApplicationRef, Injector, ComponentFactoryResolver, ViewChild } from '@angular/core';
import { PdfViewerComponent, PDFDocumentProxy } from 'ng2-pdf-viewer';
import { FileService } from '@globalCore/services/file.service';
import { StageFiles } from '@modules/flow/models/stage-files.model';
import { Flow } from '@modules/_models/flow.model';
import { Signature, SignatureType, SignatureEvent } from '@modules/flow/models/signature.model';
import { SignatureComponent } from '../signature/signature.component';
import { SignaturesService } from '@modules/flow/services/signatures.service';
@Component({
selector: 'app-signature-verification-stage',
templateUrl: './signature-verification-stage.component.html',
styleUrls: ['./signature-verification-stage.component.scss']
})
export class SignatureVerificationStageComponent implements OnInit, OnChanges {
@ViewChild(PdfViewerComponent) pdfViewer: PdfViewerComponent;
@Input() flow: Flow;
signatures$ = this._signatureService.signatures$;
currentPage: number;
totalPages = 0;
lastSignatureId = 1;
SignatureType = SignatureType;
isPartnerViewMode = false;
lastPartnerSignaturePage = 1;
stageFiles: StageFiles;
fileID: number;
filePath: string;
pdfSrc: any;
/* #region - Lifecycle */
constructor(private _app: ApplicationRef,
private _injector: Injector,
private _factoryResolver: ComponentFactoryResolver,
private _fileService: FileService,
private _signatureService: SignaturesService) { }
ngOnInit() { }
ngOnChanges() {
if (this.flow) {
this.isPartnerViewMode = this.flow.stage.id === 6;
this.fetchStageFiles();
}
}
/* #endregion */
/* #region - Fetching and handling files */
fetchStageFiles() {
const stageID = 4;
this._fileService.getStageFiles(this.flow.id, stageID, (files: StageFiles) => this.onGetStageFiles(files));
}
onGetStageFiles(files: StageFiles) {
this.fileID = files[0].files[0].id;
this.filePath = files[0].files[0].filePath;
this.showPdfFile(this.filePath);
this._signatureService.getAll(this.flow.id, this.fileID);
}
showPdfFile(fileName: string) {
const stageID = 4;
this._fileService.downloadFileFromStage(fileName, stageID, this.flow.id).then((response) => {
const file = new Blob([response], { type: 'blob' });
this.pdfSrc = URL.createObjectURL(file);
});
}
/* #endregion */
/* #region - PDFViewer actions */
scrollToPage(page: number) {
this.pdfViewer.pdfViewer.scrollPageIntoView({
pageNumber: page
});
}
showNextPartnerSignature() {
const signatures = this.signatures$.getValue();
const partnerSignatures = signatures
.filter(signature => signature.type === SignatureType.Partner)
.sort((a, b) => a.page - b.page);
for (const signature of partnerSignatures) {
if (this.lastPartnerSignaturePage < signature.page) {
this.scrollToPage(signature.page);
this.lastPartnerSignaturePage = signature.page;
break;
}
}
}
showPreviousPartnerSignature() {
const signatures = this.signatures$.getValue();
const partnerSignatures = signatures
.filter(signature => signature.type === SignatureType.Partner)
.sort((a, b) => a.page + b.page);
for (const signature of partnerSignatures) {
if (this.lastPartnerSignaturePage > signature.page) {
this.scrollToPage(signature.page);
this.lastPartnerSignaturePage = signature.page;
break;
}
}
}
/* #endregion */
/* #region - PDFViewer delegates */
onPageRendered(page: any) {
// Create a signature container for the page
const signaturesContainer = document.createElement('div');
signaturesContainer.id = 'signatures-container-' + page.pageNumber;
signaturesContainer.className = 'signatures-container';
// Append the container to the page's annotation layer div
page.source.div.appendChild(signaturesContainer);
// Append existing signatures receieved from the server
this.signatures$.subscribe(signatures => {
signatures.forEach(signature => {
if (signature.page === page.pageNumber) {
this.addSignatureFromServer(signature);
}
});
});
}
onLoadComplete(pdf: PDFDocumentProxy) {
this.totalPages = pdf.numPages;
}
/* #endregion */
/* #region - Signatures handling */
// Adds a signature received from the client/user
addSignature(signatureType: SignatureType) {
// Create a signature object
const signature: Signature = {
id: this.lastSignatureId,
type: signatureType,
imageUrl: `/assets/images/signatures/${signatureType}.png`,
flowID: this.flow.id,
fileID: this.fileID,
page: this.currentPage,
position: { x: 100, y: 100 },
size: signatureType === SignatureType.Identification ? { width: 150, height: 150 } : { width: 320, height: 55 }
};
// Send it to the server and append it to the page
this._signatureService.create(signature)
.subscribe(createdSignature => {
signature.id = createdSignature.id;
this._appendSignature(signature, true);
});
}
// Adds a signature received from the server
addSignatureFromServer(signature: Signature) {
if (!signature.imageUrl) {
signature.imageUrl = `/assets/images/signatures/${signature.type}.png`;
}
this._appendSignature(signature, false);
}
private _appendSignature(signature: Signature, isNew: boolean) {
// Resolve the SignatureComponent as a factory (so we can use it as an actual object)
const factory = this._factoryResolver.resolveComponentFactory(SignatureComponent);
// Create a native html signature element that will contain our factory
const signatureElement = document.createElement('div');
signatureElement.className = `signature ${signature.type} ${!!this.isPartnerViewMode ?? 'editable'}`;
document.getElementById('signatures-container-' + signature.page).appendChild(signatureElement);
// Inject our native element to the factory to receive a ComponentRef
const ref = factory.create(this._injector, [], signatureElement);
// Afetr the injection, we can access the component's @Input(s) and variables so we can give it the Signature object we created
ref.instance.signature = signature;
ref.instance.isEditable = !this.isPartnerViewMode;
// Subscribe to the new component's changes
ref.instance.signatureUpdated
.subscribe((object: Signature) => this.onSignatureUpdated(object));
ref.instance.signatureDeleted
.subscribe((event: SignatureEvent) => this.onSignatureDeleted(event));
// Attach the view to the app so it will get rendered to the DOM
this._app.attachView(ref.hostView);
// Add the signature to our array for reference if it is a new one
// if (isNew) {
// this.signatures$.next(this.signatures$.getValue().concat(signature));
// }
}
/* #endregion */
/* #region - Signatures delegates */
onSignatureUpdated(signature: Signature) {
this._signatureService.update(signature)
.subscribe(() => {
// Find the signature index by id and replace the object
// const foundIndex = this.signatures$.getValue().findIndex(object => object.id === signature.id);
// const tempArray = this.signatures$.getValue();
// tempArray[foundIndex] = signature;
// this.signatures$.next(tempArray);
});
}
onSignatureDeleted({ component, signature }: SignatureEvent) {
this._signatureService.delete(signature)
.subscribe(() => {
// Find the signature index by id and delete the object
// const foundIndex = this.signatures$.findIndex(object => object.id === signature.id);
// this.signatures$.splice(foundIndex, 1);
// Find the comopnent DOM element and remove it
const factory = this._factoryResolver.resolveComponentFactory(SignatureComponent);
const ref = factory.create(this._injector, [], component.nativeElement);
this._app.detachView(ref.hostView);
component.nativeElement.parentNode.removeChild(component.nativeElement);
});
}
/* #endregion */
}