| @@ -93,6 +93,7 @@ import { UploadCardsComponent } from './upload-cards/upload-cards.component'; | |||
| import {UploadInterceptor} from './lender-uploads/upload-status.interceptor'; | |||
| import { UploadDetailComponent } from './upload-detail/upload-detail.component'; | |||
| import {SafeUrlPipe} from './pipe/safe.url.pipe'; | |||
| import { ImagePopupDialogComponent } from './image-popup-dialog/image-popup-dialog.component'; | |||
| @@ -155,7 +156,8 @@ import {SafeUrlPipe} from './pipe/safe.url.pipe'; | |||
| ListIncomeComponent, | |||
| UploadCardsComponent, | |||
| UploadDetailComponent, | |||
| SafeUrlPipe | |||
| SafeUrlPipe, | |||
| ImagePopupDialogComponent | |||
| ], | |||
| imports: [ | |||
| BrowserModule, | |||
| @@ -1,7 +1,7 @@ | |||
| .container.outer{ | |||
| width:100%; | |||
| max-width:100vw; | |||
| background: url('/assets/img/body_bg.jpg') no-repeat center center fixed; | |||
| background: url('./assets/img/body_bg.jpg') no-repeat center center fixed; | |||
| -webkit-background-size: cover; | |||
| -moz-background-size: cover; | |||
| -o-background-size: cover; | |||
| @@ -0,0 +1,18 @@ | |||
| <kendo-dialog [title]="title" *ngIf="opened" (close)="close('cancel')" | |||
| [minWidth]="'200px'" [minHeight]="'200px'" [width]="dialogWidth" | |||
| [maxWidth]="'100vw'" [maxHeight]="'100vh'" | |||
| > | |||
| <div class="text-center"> | |||
| <kendo-loader *ngIf="!imageLoaded" size="large"></kendo-loader> | |||
| </div> | |||
| <img #content [src]="url | safeUrl" (load)="onLoad()" [width]="iWidth"/> | |||
| <kendo-dialog-actions> | |||
| <button kendoButton (click)="zoom(+0.1)" [icon]="'zoom-in'" [disabled]="imgWidthPercent >= 200 "><span class="k-fx-hidden">.</span></button> | |||
| <button kendoButton (click)="zoom(-0.1)" [icon]="'zoom-out'" [disabled]="imgWidthPercent <=0" ><span class="k-fx-hidden">.</span></button> | |||
| <button kendoButton (click)="originalSize()" >1:1</button> | |||
| <button kendoButton (click)="close('no')" [disabled]="true"></button> | |||
| <button kendoButton (click)="close('no')" [disabled]="true">{{imgWidthPercent | percent:'1.0'}}</button> | |||
| <button kendoButton (click)="close('yes')" [icon]="'close-circle'" primary="true">close</button> | |||
| </kendo-dialog-actions> | |||
| </kendo-dialog> | |||
| @@ -0,0 +1,25 @@ | |||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { ImagePopupDialogComponent } from './image-popup-dialog.component'; | |||
| describe('ImagePopupDialogComponent', () => { | |||
| let component: ImagePopupDialogComponent; | |||
| let fixture: ComponentFixture<ImagePopupDialogComponent>; | |||
| beforeEach(async () => { | |||
| await TestBed.configureTestingModule({ | |||
| declarations: [ ImagePopupDialogComponent ] | |||
| }) | |||
| .compileComponents(); | |||
| }); | |||
| beforeEach(() => { | |||
| fixture = TestBed.createComponent(ImagePopupDialogComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,100 @@ | |||
| import {AfterViewInit, Component, ElementRef, HostListener, Input, OnInit, ViewChild} from '@angular/core'; | |||
| @Component({ | |||
| selector: 'app-image-popup-dialog', | |||
| templateUrl: './image-popup-dialog.component.html', | |||
| styleUrls: ['./image-popup-dialog.component.scss'] | |||
| }) | |||
| export class ImagePopupDialogComponent implements OnInit{ | |||
| @Input() url = ''; | |||
| @Input() title = 'Image'; | |||
| @ViewChild('content', { read: ElementRef }) img: ElementRef; | |||
| public opened = false; | |||
| public imgWidthPercent = 1; | |||
| public iWidth = 100; | |||
| public imgOriginalWidth = 0; | |||
| public imgOriginalHeight = 0; | |||
| public imageLoaded = false; | |||
| public scrHeight = 0; | |||
| public scrWidth = 0; | |||
| public dialogWidth = 0 ; | |||
| public margin = 32; // dialog margin; | |||
| constructor() { } | |||
| ngOnInit(): void { | |||
| this.getScreenSize(); | |||
| this.initDialogWidth(); | |||
| } | |||
| @HostListener('window:resize', ['$event']) | |||
| getScreenSize(event?): void { | |||
| this.scrHeight = window.innerHeight; | |||
| this.scrWidth = window.innerWidth; | |||
| this.dialogWidth = 200; // this.scrWidth; | |||
| // console.log(this.scrHeight, this.scrWidth); | |||
| // console.log(this.scrHeight, this.scrWidth); | |||
| } | |||
| public showImg(url: string, title?: string): void { | |||
| this.initDialogWidth(); | |||
| this.title = title || 'Image'; | |||
| const ts = Date.now(); | |||
| this.url = url + '?date=' + ts; | |||
| this.opened = true; | |||
| } | |||
| private initDialogWidth(): void { | |||
| this.imageLoaded = false; | |||
| this.dialogWidth = 200; | |||
| this.iWidth = this.dialogWidth - this.margin; | |||
| } | |||
| public close(status: string): void { | |||
| this.opened = false; | |||
| } | |||
| public zoom(diff: number): void { | |||
| this.imgWidthPercent += diff; | |||
| if (this.imgWidthPercent < 0.05) { | |||
| this.imgWidthPercent = 0.05; | |||
| } | |||
| if (this.imgWidthPercent > 3){ | |||
| this.imgWidthPercent = 3; | |||
| } | |||
| this.resizeWindow(); | |||
| } | |||
| public originalSize(): void { | |||
| this.imgWidthPercent = 1; | |||
| this.resizeWindow(); | |||
| } | |||
| private resizeWindow(): void { | |||
| this.iWidth = this.imgOriginalWidth * this.imgWidthPercent; | |||
| const margin = this.margin; // the default dialog padding | |||
| if (this.iWidth + margin <= this.scrWidth) { | |||
| this.dialogWidth = this.iWidth + margin; | |||
| }else{ | |||
| this.dialogWidth = this.scrWidth; | |||
| } | |||
| console.log(this.img.nativeElement.naturalWidth, this.img.nativeElement.naturalHeight, | |||
| this.img.nativeElement.width, this.img.nativeElement.height, this.iWidth, this.imgWidthPercent); | |||
| } | |||
| public onLoad(): void { | |||
| this.imageLoaded = true; | |||
| this.imgOriginalWidth = this.img.nativeElement.naturalWidth; | |||
| this.imgOriginalHeight = this.img.nativeElement.naturalHeight; | |||
| if ( this.imgOriginalWidth > this.scrWidth) { | |||
| this.iWidth = this.scrWidth - this.margin; | |||
| }else{ | |||
| this.iWidth = this.imgOriginalWidth; | |||
| } | |||
| this.imgWidthPercent = this.iWidth / this.imgOriginalWidth; | |||
| this.resizeWindow(); | |||
| } | |||
| } | |||
| @@ -8,7 +8,7 @@ | |||
| (uploadProgress)="uploadProgress($event)" | |||
| [showFileList]="false" | |||
| [saveUrl]="uploadSaveUrl"> | |||
| <ng-template kendoUploadFileInfoTemplate let-files let map="map"> | |||
| <ng-template kendoUploadFileInfoTemplate let-files> | |||
| <div *ngIf="files!==undefined && files!== null && files[0] !== null"> | |||
| <div (click)="onClick(files)">Name: {{ files[0].name }}</div> | |||
| <div *ngIf="files[0].validationErrors !== undefined"> Cannot upload this file {{files[0]}}</div> | |||
| @@ -24,11 +24,16 @@ | |||
| </div> | |||
| <bkp-divider-shadow-bottom></bkp-divider-shadow-bottom> | |||
| <div class="container"> | |||
| <div class="row justify-content-start"> | |||
| <div class="col-lg-3 text-center" *ngFor="let p of displayedUploads"> | |||
| <div *ngIf="!loading" class="row justify-content-start"> | |||
| <div class="col-lg-3 text-center" *ngFor="let p of displayedUploads"> | |||
| <app-upload-cards [uploadId]="p"></app-upload-cards> | |||
| </div> | |||
| </div> | |||
| <div *ngIf="loading" class="row justify-content-center"> | |||
| <div class="col-lg-1 text-center loader"> | |||
| <kendo-loader [size]="'large'"></kendo-loader> | |||
| </div> | |||
| </div> | |||
| <div class="vertical-spacer"></div> | |||
| </div> | |||
| @@ -41,6 +46,4 @@ | |||
| (pageChange)="onPageChange($event)"> | |||
| </kendo-datapager> | |||
| </div> | |||
| @@ -3,6 +3,11 @@ div.workarea{ | |||
| position:relative; | |||
| background-color: #46495b; | |||
| overflow: scroll; | |||
| div.row div.loader{ | |||
| top: 45%; | |||
| position:absolute; | |||
| } | |||
| } | |||
| div.file-manager-bar{ | |||
| @@ -4,6 +4,8 @@ import {AuthService} from '../service/auth.service'; | |||
| import {range} from '@progress/kendo-angular-dateinputs/dist/es2015/util'; | |||
| import {Router} from '@angular/router'; | |||
| import {PageChangeEvent} from '@progress/kendo-angular-pager'; | |||
| import {ImagePopupDialogComponent} from '../image-popup-dialog/image-popup-dialog.component'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| @Component({ | |||
| selector: 'app-lender-uploads', | |||
| @@ -20,6 +22,7 @@ export class LenderUploadsComponent implements OnInit { | |||
| private uploads: SuccessEvent[] = []; | |||
| public value = 0 ; | |||
| public map: Map<string, SuccessEvent> = new Map<string, SuccessEvent>(); | |||
| public loading = false; | |||
| uploadSaveUrl = 'https://svr2021.lawipac.com:8080/api/v1/lender-upload/'; // should represent an actual API endpoint | |||
| uploadRemoveUrl = 'https://svr2021.lawipac.com:8080/api/v1/lender-upload-remove/'; // should represent an actual API endpoint | |||
| @@ -37,7 +40,7 @@ export class LenderUploadsComponent implements OnInit { | |||
| allowedExtensions: ['.pdf', '.xls', '.xlsx'] | |||
| }; | |||
| constructor(private auth: AuthService, private router: Router) { | |||
| constructor(private auth: AuthService, private router: Router, private uas: UploadAttachService) { | |||
| } | |||
| @@ -45,6 +48,7 @@ export class LenderUploadsComponent implements OnInit { | |||
| this.uploadSaveUrl = this.auth.getUrl('lender-upload/'); | |||
| this.uploadRemoveUrl = this.auth.getUrl('lender-upload-remove/'); | |||
| this.loadDisplayedUploads(); | |||
| } | |||
| @@ -97,9 +101,22 @@ export class LenderUploadsComponent implements OnInit { | |||
| this.filteredUploads = this.allUploads ; | |||
| this.displayedUploads = this.filteredUploads.slice(this.skip, this.skip + this.pageSize); | |||
| this.total = this.displayedUploads.length; | |||
| } | |||
| private loadAllUploads(): void{ | |||
| this.loading = true; | |||
| // this.uas.getUploadMetaList(this.skip, this.skip + this.pageSize).subscribe( | |||
| // resp => { | |||
| // this.loading = false; | |||
| // } | |||
| // ); | |||
| } | |||
| public onFilterUploads(hint: string): void { | |||
| this.loadDisplayedUploads(); | |||
| } | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| export class AnalysisAaaModel { | |||
| Year: number; | |||
| Month: number; | |||
| LoanNumber: string; | |||
| Settlement: Date; | |||
| LoanAmount: number; | |||
| Balance: number; | |||
| InTrail: number; | |||
| constructor(payload: Partial<AnalysisAaaModel>) { | |||
| this.Year = payload.Year || 0; | |||
| this.Month = payload.Month || 0; | |||
| this.LoanNumber = payload.LoanNumber || ''; | |||
| this.Settlement = new Date(payload.Settlement); | |||
| this.LoanAmount = payload.LoanAmount || 0; | |||
| this.Balance = payload.Balance || 0; | |||
| this.InTrail = payload.Year || 0; | |||
| } | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import {UploadModel} from './upload.model'; | |||
| import {UploadMetaModel} from './uploadMetaModel'; | |||
| import {PayInModel} from './pay-in.model'; | |||
| import {FunderAaaTrailModel} from './funder.aaa.trail.model'; | |||
| import {PayInAAARowModel} from './Pay.In.AAA.Row.model'; | |||
| export class UploadAnalysisModel { | |||
| public Id: number; | |||
| @@ -13,7 +13,7 @@ export class UploadAnalysisModel { | |||
| public IsDuplicate: boolean; | |||
| public Uid?: string; // client side unique id when upload | |||
| public Input?: UploadModel; | |||
| public Input?: UploadMetaModel; | |||
| constructor(payload: Partial<UploadAnalysisModel>){ | |||
| this.Id = payload.Id || 0 ; | |||
| @@ -44,7 +44,7 @@ export class UploadAnalysisModel { | |||
| this.IsDuplicate = payload.IsDuplicate || false; | |||
| this.Uid = payload.Uid || '' ; | |||
| this.Input = new UploadModel(payload.Input || {}); | |||
| this.Input = new UploadMetaModel(payload.Input || {}); | |||
| } | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| export class UploadModel { | |||
| export class UploadMetaModel { | |||
| public Id: number; | |||
| public Mime: string; | |||
| public Format: string; | |||
| public FileName: string; | |||
| public Ts: Date; | |||
| public By: string; | |||
| @@ -9,9 +9,9 @@ export class UploadModel { | |||
| public Size: number; | |||
| public Sha256: string; | |||
| constructor(payload: Partial<UploadModel>) { | |||
| constructor(payload: Partial<UploadMetaModel>) { | |||
| this.Id = payload.Id || 0; | |||
| this.Mime = payload.Mime || ''; | |||
| this.Format = payload.Format || ''; | |||
| this.FileName = payload.FileName || ''; | |||
| this.Ts = new Date (payload.Ts || new Date()); | |||
| this.By = payload.By || ''; | |||
| @@ -19,4 +19,9 @@ export class UploadModel { | |||
| this.Size = payload.Size || 0; | |||
| this.Sha256 = payload.Sha256 || ''; | |||
| } | |||
| get iconUrl(): string { | |||
| const t = this.Format.replace( '/', '-'); | |||
| return location.origin +'/assets/img/mime/' + t + '.png'; | |||
| } | |||
| } | |||
| @@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'; | |||
| import {HttpClient} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {Observable} from 'rxjs'; | |||
| import {UploadModel} from '../models/upload.model'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadAnalysisModel} from '../models/upload.analysis.model'; | |||
| @@ -11,8 +11,8 @@ export class UploadAttachService { | |||
| constructor(private http: HttpClient, private auth: AuthService) { | |||
| } | |||
| public getUploadMeta(id: number): Observable<UploadModel> { | |||
| return this.http.get<UploadModel>(this.auth.getUrl('upload-meta/' + id)); | |||
| public getUploadMeta(id: number): Observable<UploadMetaModel> { | |||
| return this.http.get<UploadMetaModel>(this.auth.getUrl('upload-meta/' + id)); | |||
| } | |||
| public getUploadAnalysis(id: number): Observable<UploadAnalysisModel> { | |||
| @@ -20,13 +20,23 @@ export class UploadAttachService { | |||
| } | |||
| public getUploadAsJpgUrl(id: number): string { | |||
| return this.auth.getUrl('upload-as-image/' + id); | |||
| const ts = Date.now(); | |||
| return this.auth.getUrl('upload-as-image/' + id + '?date=' + ts); | |||
| } | |||
| public getUploadAsJpgUrlDefault(): string { | |||
| return this.auth.getUrl('upload-as-image/default'); | |||
| } | |||
| public getUploadAsPdfUrl(id: number): string { | |||
| const ts = Date.now(); | |||
| return this.auth.getUrl('upload-as-pdf/' + id + '?date=' + ts); | |||
| } | |||
| public getUploadAsPdfUrlDefault(): string { | |||
| const ts = Date.now(); | |||
| return this.auth.getUrl('upload-as-pdf/default' + '?date=' + ts); | |||
| } | |||
| public getUploadAsPdfUrlForDownload(id: number): string { | |||
| return this.auth.getUrl('upload-as-pdf/' + id + '?download=force'); | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| <div class="card-wrapper"> | |||
| <div class="file-type-image" [ngStyle]="{'background-image': 'url(https://via.placeholder.com/32x32)'}"></div> | |||
| <kendo-card class="upload-card-content upload-thumb" [width]="'200px'" [ngStyle]="{'background-image' : thumbImage}"> | |||
| <div class="file-type-image" [ngStyle]="{'background-image': 'url('+ icon +')'}"></div> | |||
| <kendo-card class="upload-card-content upload-thumb" [width]="'200px'" [ngStyle]="{'background-image' : 'url(' + thumbImage + ')'}"> | |||
| <kendo-card-header class="k-hbox"> | |||
| <p> some title </p> | |||
| @@ -17,6 +17,6 @@ | |||
| </kendo-card> | |||
| </div> | |||
| <app-image-popup-dialog #imgBox></app-image-popup-dialog> | |||
| @@ -32,18 +32,17 @@ div.card-wrapper{ | |||
| } | |||
| .file-type-image{ | |||
| width: 32px; | |||
| border-radius:100%; | |||
| // border-radius:100%; | |||
| background-repeat: no-repeat; | |||
| background-size: cover; | |||
| border:1px white solid; | |||
| box-shadow: inset 1px 1px 0 #b7b7b7; | |||
| // border:1px white solid; | |||
| // box-shadow: inset 1px 1px 0 #b7b7b7; | |||
| z-index: 2; | |||
| width: 32px; | |||
| height: 32px; | |||
| position: absolute; | |||
| margin-right: 10px; | |||
| right : 30px; | |||
| left : 160px; | |||
| top: 20px; | |||
| } | |||
| @@ -1,5 +1,11 @@ | |||
| import {Component, Input, OnInit} from '@angular/core'; | |||
| import {Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; | |||
| import {Router} from '@angular/router'; | |||
| import {ImagePopupDialogComponent} from '../image-popup-dialog/image-popup-dialog.component'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| @Component({ | |||
| selector: 'app-upload-cards', | |||
| @@ -8,13 +14,25 @@ import {Router} from '@angular/router'; | |||
| }) | |||
| export class UploadCardsComponent implements OnInit { | |||
| @Input() uploadId: number; | |||
| @Input() icon = '../../assets/img/pdf.jpg'; | |||
| @ViewChild('imgBox', {static: true}) imgBox: ImagePopupDialogComponent; | |||
| public uploadMeta: UploadMetaModel = new UploadMetaModel({}); | |||
| public thumbImage = 'url(https://svr2021.lawipac.com:8080/api/v1/upload-as-thumbnail/31)'; | |||
| public thumbImage = 'https://svr2021.lawipac.com:8080/api/v1/upload-as-thumbnail/31'; | |||
| public fullImage = ''; | |||
| constructor(private router: Router) { } | |||
| constructor(private router: Router, private auth: AuthService, private uas: UploadAttachService) { } | |||
| ngOnInit(): void { | |||
| this.thumbImage = 'url(https://svr2021.lawipac.com:8080/api/v1/upload-as-thumbnail/' + this.uploadId + ')'; | |||
| let url = this.auth.getUrl('upload-as-thumbnail/' + this.uploadId); | |||
| this.thumbImage = url ; | |||
| url = this.auth.getUrl('upload-as-image/' + this.uploadId); | |||
| this.fullImage = url; | |||
| this.uas.getUploadMeta(this.uploadId).subscribe( resp => { | |||
| this.uploadMeta = new UploadMetaModel(resp); | |||
| this.icon = this.uploadMeta.iconUrl; | |||
| }); | |||
| } | |||
| public onNavigateTo(id: number ): void{ | |||
| @@ -24,6 +42,12 @@ export class UploadCardsComponent implements OnInit { | |||
| this.router.navigate(['/upload-details/' + id]); | |||
| } | |||
| public onSearch(id: number ): void{ | |||
| this.router.navigate(['/upload-details/' + id]); | |||
| this.popupImage(this.fullImage); | |||
| // this.popupImage(this.thumbImage); | |||
| } | |||
| public popupImage(url: string): void { | |||
| this.imgBox.showImg(url, this.uploadId + ' - ' + this.uploadMeta.FileName); | |||
| } | |||
| } | |||
| @@ -1,57 +1,100 @@ | |||
| <div class="workspace"> | |||
| <kendo-tabstrip [tabPosition]="'left'" class="fullheight-tab upload-details" | |||
| (tabSelect)="onTabSelect($event)" | |||
| [animate]="false" | |||
| > | |||
| <kendo-tabstrip-tab title="PreView" [selected]="true"> | |||
| <ng-template kendoTabContent> | |||
| <div class="dark-panel text-center" > | |||
| <div class="main-image-content" > | |||
| <img [src]="uploadAsPicUrl | safeUrl"> | |||
| </div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| <kendo-tabstrip-tab title="Content" cssClass="uploads-panel"> | |||
| <ng-template kendoTabContent class="dark-panel"> | |||
| <div class="dark-panel text-center"> | |||
| <div class="main-content" [ngClass]="{'init-pdf': !initAnimation, 'ready-pdf': initAnimation}"> | |||
| <kendo-loader *ngIf="!iframeLoaded || !initAnimation" [size]="'large'"></kendo-loader> | |||
| <iframe #pdf height="100%" | |||
| [ngClass]="{'init-iframe': !iframeLoaded, 'ready-iframe': iframeLoaded}" | |||
| [src]="uploadAsPdfUrl | safeUrl" | |||
| (loadstart)="finishedLoading('loadstart')" | |||
| (load)="finishedLoading('load')" ></iframe> | |||
| </div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| <kendo-tabstrip-tab title="AI Analysis"> | |||
| <ng-template kendoTabContent> | |||
| <div *ngIf="!scandone" class="dark-panel text-center" > | |||
| <div *ngIf="scanning" class="overlay" [ngClass]="{'scanning': scanning, 'fade-out': scandone }"></div> | |||
| <div *ngif="!scandone" class="main-content" [ngClass]="{'init-pdf': !initAnimation, 'ready-pdf': initAnimation, 'fade-out': scandone}"> | |||
| <kendo-loader *ngIf="!iframeLoaded || !initAnimation" [size]="'large'"></kendo-loader> | |||
| <iframe #pdf height="100%" | |||
| [ngClass]="{'init-iframe': !iframeLoaded, 'ready-iframe': iframeLoaded}" | |||
| [src]="uploadAsPdfUrl | safeUrl" | |||
| (loadstart)="finishedLoading('loadstart')" | |||
| (load)="finishedLoading('load')" ></iframe> | |||
| </div> | |||
| </div> | |||
| <p> {{ ua.Input.FileName }}</p> | |||
| <p> {{ ua.Input.Id }}</p> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| </kendo-tabstrip> | |||
| <kendo-floatingactionbutton #fab class="init-fab" [ngClass]="{'init-fab': !initAnimation, 'ready-fab': initAnimation}" | |||
| <kendo-splitter orientation="vertical"> | |||
| <kendo-splitter-pane [resizable]="true" > | |||
| <div class="pane-content"> | |||
| <kendo-tabstrip #tabs [tabPosition]="'left'" class="fullheight-tab upload-details" | |||
| (tabSelect)="onTabSelect($event)" | |||
| [animate]="true" | |||
| > | |||
| <kendo-tabstrip-tab [title]="tabTitle[0]" [selected]="true"> | |||
| <ng-template kendoTabContent> | |||
| <div class="dark-panel text-center preview" > | |||
| <div *ngIf="!PreviewImgLoaded" class="text-center"> | |||
| <kendo-loader size="large"></kendo-loader> | |||
| </div> | |||
| <div class="main-image-content" | |||
| [ngClass]="{'image-not-loaded': !PreviewImgLoaded, 'image-loaded': PreviewImgLoaded }" > | |||
| <img [src]="uploadAsPicUrl | safeUrl" (load)="onImgPreviewLoaded()"> | |||
| </div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| <kendo-tabstrip-tab [title]="tabTitle[1]" cssClass="uploads-panel"> | |||
| <ng-template kendoTabContent class="dark-panel"> | |||
| <div class="dark-panel text-center"> | |||
| <div class="main-content" [ngClass]="{'init-pdf': !initAnimation, 'ready-pdf': initAnimation}"> | |||
| <kendo-loader *ngIf="!iframeLoaded || !initAnimation" [size]="'large'"></kendo-loader> | |||
| <iframe #pdf height="100%" | |||
| [ngClass]="{'init-iframe': !iframeLoaded, 'ready-iframe': iframeLoaded}" | |||
| [src]="uploadAsPdfUrl | safeUrl" | |||
| (loadstart)="finishedLoading('loadstart')" | |||
| (load)="finishedLoading('load')" ></iframe> | |||
| </div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| <kendo-tabstrip-tab [title]="tabTitle[2]"> | |||
| <ng-template kendoTabContent> | |||
| <div *ngIf="!scanDone" class="dark-panel text-center preview" > | |||
| <div *ngIf="!ScanImgLoaded" class="text-center"> | |||
| <kendo-loader size="large"></kendo-loader> | |||
| </div> | |||
| <div *ngIf="scanning" class="overlay" | |||
| [ngClass]="{'scanning': scanning, 'fade-out': scanDone }"> | |||
| <hr class="red-scan-line" /> | |||
| </div> | |||
| <div *ngIf="!scanDone || scanning" class="main-image-content" | |||
| [ngClass]="{'image-not-loaded': !ScanImgLoaded, 'image-loaded': ScanImgLoaded }" > | |||
| <img [src]="uploadAsPicUrl | safeUrl" (load)="onImgScanLoaded()"> | |||
| </div> | |||
| </div> | |||
| <div *ngIf="scanDone" class="analysis-result"> | |||
| <kendo-grid *ngIf="ua.Funder ==='AAA Financial'" [data]="analysisAAA"> | |||
| <ng-template kendoGridToolbarTemplate> | |||
| {{ua.Funder }} | |||
| <button kendoGridAddCommand icon="plus" >Add new Income</button> | |||
| Show Uploads <kendo-switch></kendo-switch> | |||
| </ng-template> | |||
| <kendo-grid-column field="Year"> </kendo-grid-column> | |||
| <kendo-grid-column field="Month"> </kendo-grid-column> | |||
| <kendo-grid-column field="LoanNumber"> </kendo-grid-column> | |||
| <kendo-grid-column field="Settlement"> </kendo-grid-column> | |||
| <kendo-grid-column field="Amount"> </kendo-grid-column> | |||
| <kendo-grid-column field="Balance"> </kendo-grid-column> | |||
| <kendo-grid-column field="InTrail"> </kendo-grid-column> | |||
| </kendo-grid> | |||
| <div *ngIf="ua.Funder ===''"> | |||
| <kendo-grid> </kendo-grid> | |||
| </div> | |||
| </div> | |||
| </ng-template> | |||
| </kendo-tabstrip-tab> | |||
| </kendo-tabstrip> | |||
| </div> | |||
| </kendo-splitter-pane> | |||
| <kendo-splitter-pane [resizable]="true" [collapsible]="true" [collapsed]="!resizeSplitter" | |||
| (collapsedChange)="onPayInCollapsed($event)" | |||
| (sizeChange)="onPayInSizeChange($event)" | |||
| [(size)]="PayInSize"> | |||
| <div class="pane-content"> | |||
| <h3>Outer splitter / Bottom pane</h3> | |||
| <p>Non-resizable and non-collapsible.</p> | |||
| </div> | |||
| </kendo-splitter-pane> | |||
| </kendo-splitter> | |||
| <kendo-floatingactionbutton #fab class="init-fab" | |||
| [ngClass]="{'init-fab': !initAnimation && !resizeSplitter, 'ready-fab': initAnimation &&!resizeSplitter}" | |||
| [icon]="'table'" | |||
| [offset]="fabOffset" | |||
| [align]="{ horizontal: 'start', vertical: 'bottom' }" | |||
| (click)="onClickFloatActionButton($event)" | |||
| [title]="'List all Uploads'" | |||
| @@ -60,3 +103,6 @@ | |||
| > | |||
| </kendo-floatingactionbutton> | |||
| </div> | |||
| @@ -5,6 +5,13 @@ | |||
| div.workspace { | |||
| height: calc(100vh - 48px); | |||
| overflow: hidden; | |||
| kendo-splitter { | |||
| height:100%; | |||
| .pane-content{ | |||
| height:100%; | |||
| } | |||
| } | |||
| } | |||
| .fullheight-tab{ | |||
| @@ -21,6 +28,11 @@ div.workspace { | |||
| overflow: hidden; | |||
| position:relative; | |||
| &.preview{ | |||
| height: unset; | |||
| min-height: 100%; | |||
| } | |||
| .main-content, | |||
| .main-image-content{ | |||
| position:relative; | |||
| @@ -34,7 +46,7 @@ div.workspace { | |||
| .main-image-content{ | |||
| display:block; | |||
| text-align: center; | |||
| overflow: scroll; | |||
| margin-bottom: 30px; | |||
| img { | |||
| width:100%; | |||
| } | |||
| @@ -72,11 +84,14 @@ div.workspace { | |||
| transition: all 0.1s ease-in-out; | |||
| } | |||
| } | |||
| kendo-floatingactionbutton { | |||
| width: 52px; | |||
| } | |||
| .init-fab{ | |||
| top : -10px !important; | |||
| top : 50vh !important; | |||
| transition: all 1s ease-in-out; | |||
| } | |||
| @@ -86,24 +101,87 @@ div.workspace { | |||
| } | |||
| div.overlay { | |||
| height: 10%; | |||
| background-color: black; | |||
| height: 100px; | |||
| background-color: #19c7ef; | |||
| opacity: 0.5; | |||
| z-index: 10; | |||
| position: absolute; | |||
| width: 100%; | |||
| background-image: url('../../assets/img/scanbar.gif'); | |||
| background-size: cover; | |||
| background-repeat: no-repeat; | |||
| position: absolute; | |||
| top:20px; | |||
| background-blend-mode: color; // for background image | |||
| mix-blend-mode: hard-light; // for background | |||
| } | |||
| .scanning{ | |||
| animation: scanning 1s infinite; | |||
| animation: scanning 3s infinite; | |||
| } | |||
| .image-not-loaded{ | |||
| top: 100vh; | |||
| } | |||
| .image-loaded{ | |||
| top: 0; | |||
| } | |||
| @keyframes scanning { | |||
| 50% { | |||
| transform: translateY(80vh); | |||
| transform: translateY(100vh); | |||
| } | |||
| } | |||
| @keyframes shrinking { | |||
| 50% { | |||
| height: 10px; | |||
| background-color: yellow; | |||
| } | |||
| } | |||
| .fade-out { | |||
| opacity: 0; | |||
| transition: all 0.5s ease-out; | |||
| } | |||
| .blink_me { | |||
| animation: blinker 1s linear infinite; | |||
| } | |||
| .red-scan-line{ | |||
| color: red; | |||
| width: 100%; | |||
| border-top: 3px solid red; | |||
| box-shadow: 1px 1px 10px red; | |||
| animation: blinker 0.1s linear infinite; | |||
| position: absolute; | |||
| top: 80%; | |||
| } | |||
| @keyframes blinker { | |||
| 50% { | |||
| opacity: 0.1; | |||
| } | |||
| } | |||
| .scanning > .red-scan-line { | |||
| animation: top-down 3s infinite; | |||
| } | |||
| @keyframes top-down { | |||
| 0% { top:80%;} | |||
| 49% { top:80%; } | |||
| 50% { top: 0; } | |||
| 99% { top: 0; } | |||
| 100%{ top: 80%;} | |||
| } | |||
| div.analysis-result{ | |||
| height: 100%; | |||
| width: 100%; | |||
| background-color: lightgoldenrodyellow; | |||
| kendo-grid{ | |||
| height: 100%; | |||
| } | |||
| } | |||
| @@ -1,10 +1,11 @@ | |||
| import {Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; | |||
| import {AfterViewChecked, AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewEncapsulation} from '@angular/core'; | |||
| import {ActivatedRoute, Router} from '@angular/router'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| import {UploadAnalysisModel} from '../models/upload.analysis.model'; | |||
| import {UploadModel} from '../models/upload.model'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {FloatingActionButtonComponent} from '@progress/kendo-angular-buttons'; | |||
| import {DrawerSelectEvent} from '@progress/kendo-angular-layout'; | |||
| import {DrawerSelectEvent, TabStripComponent} from '@progress/kendo-angular-layout'; | |||
| import {AnalysisAaaModel} from '../models/analysis.aaa.model'; | |||
| @@ -13,18 +14,34 @@ import {DrawerSelectEvent} from '@progress/kendo-angular-layout'; | |||
| templateUrl: './upload-detail.component.html', | |||
| styleUrls: ['./upload-detail.component.scss'] | |||
| }) | |||
| export class UploadDetailComponent implements OnInit { | |||
| export class UploadDetailComponent implements OnInit, AfterViewInit { | |||
| @Input() uploadId: number; | |||
| @Input() ua: UploadAnalysisModel = new UploadAnalysisModel({}); | |||
| @ViewChild('fab', {static: true}) fab: FloatingActionButtonComponent; | |||
| @ViewChild('pdf', {static: false}) pdf: ElementRef; | |||
| @ViewChild('tabs', {static: true}) tab: TabStripComponent; | |||
| // 'http://africau.edu/images/default/sample.pdf'; | |||
| public uploadAsPicUrl = 'https://svr2021.lawipac.com:8080/api/v1/upload-as-pdf/default'; | |||
| public uploadAsPdfUrl = ''; | |||
| public initAnimation = false; | |||
| public iframeLoaded = false; | |||
| public scanning = false; | |||
| public scandone = false; | |||
| public scanDone = false; | |||
| public analysisIsDone = false; | |||
| public ScanImgLoaded = false; | |||
| public PreviewImgLoaded = false; | |||
| public resizeSplitter = false; | |||
| public PayInSize = '300px'; | |||
| public fabOffset = { x: '10px', y: this.PayInSize }; | |||
| @Input() public currentTab = 0; | |||
| public tabText: string[] = ['Preview', 'Content', 'Analysis']; | |||
| public tabTitle: string[] = this.tabText; | |||
| public analysisAAA: AnalysisAaaModel[] = []; | |||
| constructor(private us: UploadAttachService, private actRoute: ActivatedRoute, private router: Router) { } | |||
| ngOnInit(): void { | |||
| @@ -40,29 +57,60 @@ export class UploadDetailComponent implements OnInit { | |||
| } | |||
| this.loadUploadMeta(); | |||
| // this.loadUploadAnalysis(); | |||
| // this.fabOffset = { y : '-1px' }; | |||
| this.fabSlideIn(); | |||
| // this.changeTabTitle(); | |||
| // this.currentTab=2; | |||
| setTimeout(() => { | |||
| this.resizeSplitter = true; | |||
| }, 2000); | |||
| } | |||
| ngAfterViewInit(): void { | |||
| setTimeout(() => { // if I don't use this, it complains value has been changed after being checked. | |||
| this.tab.selectTab(this.currentTab); | |||
| }, 100); | |||
| } | |||
| private loadUploadAnalysis(): void { | |||
| this.analysisIsDone = false; | |||
| this.us.getUploadAnalysis(this.uploadId).subscribe( | |||
| resp => { | |||
| this.scandone = true; | |||
| this.scanning = false; | |||
| this.ua = new UploadAnalysisModel(resp); | |||
| this.uploadId = this.ua.Id; | |||
| this.ua = new UploadAnalysisModel(resp); | |||
| // this.TranslateForDisplay() // TODO: use database for this structure | |||
| this.uploadId = this.ua.Id; | |||
| this.analysisIsDone = true; | |||
| }, err => { | |||
| this.analysisIsDone = true; | |||
| }, () => { | |||
| this.analysisIsDone = true; | |||
| } | |||
| ); | |||
| } | |||
| private loadUploadMeta(): void { | |||
| this.us.getUploadMeta(this.uploadId).subscribe( | |||
| resp => { | |||
| this.ua.Input = new UploadModel(resp); | |||
| this.ua.Input = new UploadMetaModel(resp); | |||
| } | |||
| ); | |||
| } | |||
| private startScanningUI(): void { | |||
| this.resizeSplitter = false; | |||
| this.scanning = true; | |||
| let count = 0; | |||
| const t = setInterval(() => { | |||
| count ++; | |||
| if (count > 30 && this.analysisIsDone) { // 30 should be in sync with scss 3s | |||
| this.scanDone = true; | |||
| this.scanning = false; | |||
| this.resizeSplitter = true; | |||
| clearInterval(t); | |||
| } | |||
| }, 100); | |||
| } | |||
| private fabSlideIn(): void { | |||
| setTimeout(() => {this.initAnimation = true; }, 1000); | |||
| } | |||
| @@ -72,24 +120,53 @@ export class UploadDetailComponent implements OnInit { | |||
| } | |||
| public finishedLoading(status: string ): void{ | |||
| if ( this.pdf === undefined ) { | |||
| if (this.pdf === undefined) { | |||
| return; | |||
| } | |||
| const el = this.pdf.nativeElement; | |||
| if ( el !== undefined && el.src === this.uploadAsPdfUrl) { | |||
| console.log('yes true',this); | |||
| this.iframeLoaded = true; | |||
| } | |||
| const el = this.pdf.nativeElement; | |||
| if (el !== undefined && el.src === this.uploadAsPdfUrl) { | |||
| console.log('yes true', this); | |||
| this.iframeLoaded = true; | |||
| } | |||
| } | |||
| public onTabSelect( select: DrawerSelectEvent): void { | |||
| if ( select.index === 2 && ! this.iframeLoaded ) { | |||
| select.preventDefault(); | |||
| return; | |||
| } | |||
| if ( ! this.scandone ) { | |||
| this.scanning = true; | |||
| this.currentTab = select.index; | |||
| this.changeTabTitle(); | |||
| if ( select.index === 2 && ! this.scanDone ) { | |||
| this.loadUploadAnalysis(); | |||
| } | |||
| } | |||
| private changeTabTitle(): void { | |||
| this.tabTitle = []; | |||
| for ( let i = 0; i < this.tabText.length; i++ ) { | |||
| if ( i === this.currentTab ){ | |||
| this.tabTitle[i] = '☒ ' + this.tabText[i]; | |||
| }else{ | |||
| this.tabTitle[i] = '☐ ' + this.tabText[i]; | |||
| } | |||
| } | |||
| } | |||
| public onImgPreviewLoaded(): void { | |||
| this.PreviewImgLoaded = true; | |||
| } | |||
| public onImgScanLoaded(): void { | |||
| this.ScanImgLoaded = true; | |||
| this.startScanningUI(); | |||
| } | |||
| public onPayInCollapsed( closed: boolean): void { | |||
| console.log(this); | |||
| this.resizeSplitter = !closed; | |||
| return ; | |||
| } | |||
| public onPayInSizeChange(s: string): void { | |||
| this.resizeSplitter = true; | |||
| this.fabOffset = {x: '10px', y: s}; | |||
| } | |||
| } | |||