angular материал cdk перетаскивание элемента автопрокрутки не работает - PullRequest
0 голосов
/ 15 апреля 2020

Я использую angular материал, чтобы перетащить элемент (подпись) и поместить элемент в файл PDF на клиенте с помощью библиотеки pdf-viewer. Это нормально работает, и я ставлю подпись на бэкэнд (php с fpdf и fpdi).

Проблема в том, что когда клиент перетаскивает подпись в нижнюю и верхнюю часть контейнера, пользователь должен вручную прокрутить страницу, а затем поставить подпись. Я думал, что angular материал имеет автопрокрутку по умолчанию, но он не работает.

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}`);
    }
}

Родительский компонент.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>

Родительский компонент.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 */
}
...