mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	rework of the front end components
This commit is contained in:
		| @@ -32,7 +32,7 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|     this.successSubscription = this.consumerStatusService.onDocumentConsumptionFinished().subscribe(status => { | ||||
|       this.toastService.show({title: "Document added", delay: 10000, content: `Document ${status.filename} was added to paperless.`, actionName: "Open document", action: () => { | ||||
|         this.router.navigate(['documents', status.document_id]) | ||||
|         this.router.navigate(['documents', status.documentId]) | ||||
|       }}) | ||||
|     }) | ||||
|  | ||||
| @@ -42,6 +42,4 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|  | ||||
|   } | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,16 @@ | ||||
|  | ||||
|       </ngx-file-drop> | ||||
|     </form> | ||||
|     <div *ngIf="uploadVisible" class="mt-3"> | ||||
|       <p i18n>{uploadStatus.length, plural, =1 {Uploading file...} =other {Uploading {{uploadStatus.length}} files...}}</p> | ||||
|       <ngb-progressbar [value]="loadedSum" [max]="totalSum" [striped]="true" [animated]="uploadStatus.length > 0"> | ||||
|     <div *ngFor="let status of getStatus()"> | ||||
|       <p>{{status.filename}}: {{status.message}}</p> | ||||
|       <ngb-progressbar [value]="status.getProgress()" [max]="1" [striped]="true" [animated]="!isFinished(status)" [type]="getType(status)"> | ||||
|       </ngb-progressbar> | ||||
|  | ||||
|       <div *ngIf="isFinished(status)" class="mb-2"> | ||||
|         <button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary mr-2" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">Open document</button> | ||||
|         <button class="btn btn-sm btn-outline-secondary" (click)="dismiss(status)">Dismiss</button> | ||||
|       </div> | ||||
|      | ||||
|     </div> | ||||
|   </div> | ||||
| </app-widget-frame> | ||||
| @@ -1,15 +1,10 @@ | ||||
| import { HttpEventType } from '@angular/common/http'; | ||||
| import { Component, OnInit } from '@angular/core'; | ||||
| import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'; | ||||
| import { ConsumerStatusService, FileStatus, FileStatusPhase } from 'src/app/services/consumer-status.service'; | ||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | ||||
| import { ToastService } from 'src/app/services/toast.service'; | ||||
|  | ||||
|  | ||||
| interface UploadStatus { | ||||
|   loaded: number | ||||
|   total: number | ||||
| } | ||||
|  | ||||
| @Component({ | ||||
|   selector: 'app-upload-file-widget', | ||||
|   templateUrl: './upload-file-widget.component.html', | ||||
| @@ -17,7 +12,34 @@ interface UploadStatus { | ||||
| }) | ||||
| export class UploadFileWidgetComponent implements OnInit { | ||||
|  | ||||
|   constructor(private documentService: DocumentService, private toastService: ToastService) { } | ||||
|   constructor( | ||||
|     private documentService: DocumentService, | ||||
|     private consumerStatusService: ConsumerStatusService | ||||
|   ) { } | ||||
|  | ||||
|   getStatus() { | ||||
|     return this.consumerStatusService.consumerStatus | ||||
|   } | ||||
|  | ||||
|   isFinished(status: FileStatus) { | ||||
|     return status.phase == FileStatusPhase.FAILED || status.phase == FileStatusPhase.SUCCESS | ||||
|   } | ||||
|  | ||||
|   getType(status: FileStatus) { | ||||
|     switch (status.phase) { | ||||
|       case FileStatusPhase.PROCESSING: | ||||
|       case FileStatusPhase.UPLOADING: | ||||
|           return "primary" | ||||
|       case FileStatusPhase.FAILED: | ||||
|         return "danger" | ||||
|       case FileStatusPhase.SUCCESS: | ||||
|         return "success" | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   dismiss(status: FileStatus) { | ||||
|     this.consumerStatusService.dismiss(status) | ||||
|   } | ||||
|    | ||||
|   ngOnInit(): void { | ||||
|   } | ||||
| @@ -28,54 +50,37 @@ export class UploadFileWidgetComponent implements OnInit { | ||||
|   public fileLeave(event){ | ||||
|   } | ||||
|  | ||||
|   uploadStatus: UploadStatus[] = [] | ||||
|   completedFiles = 0 | ||||
|  | ||||
|   uploadVisible = false | ||||
|  | ||||
|   get loadedSum() { | ||||
|     return this.uploadStatus.map(s => s.loaded).reduce((a,b) => a+b, this.completedFiles > 0 ? 1 : 0) | ||||
|   } | ||||
|  | ||||
|   get totalSum() { | ||||
|     return this.uploadStatus.map(s => s.total).reduce((a,b) => a+b, 1) | ||||
|   } | ||||
|  | ||||
|   public dropped(files: NgxFileDropEntry[]) { | ||||
|     for (const droppedFile of files) { | ||||
|       if (droppedFile.fileEntry.isFile) { | ||||
|       let uploadStatusObject: UploadStatus = {loaded: 0, total: 1} | ||||
|       this.uploadStatus.push(uploadStatusObject) | ||||
|       this.uploadVisible = true | ||||
|  | ||||
|       const fileEntry = droppedFile.fileEntry as FileSystemFileEntry; | ||||
|         fileEntry.file((file: File) => { | ||||
|           let formData = new FormData() | ||||
|           formData.append('document', file, file.name) | ||||
|           let status = this.consumerStatusService.newFileUpload() | ||||
|           status.filename = file.name | ||||
|  | ||||
|           this.documentService.uploadDocument(formData).subscribe(event => { | ||||
|             if (event.type == HttpEventType.UploadProgress) { | ||||
|               uploadStatusObject.loaded = event.loaded | ||||
|               uploadStatusObject.total = event.total | ||||
|               status.updateProgress(FileStatusPhase.UPLOADING, event.loaded, event.total) | ||||
|             } else if (event.type == HttpEventType.Response) { | ||||
|               this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1) | ||||
|               this.completedFiles += 1 | ||||
|               this.toastService.showInfo($localize`The document has been uploaded and will be processed by the consumer shortly.`) | ||||
|               status.taskId = event.body["task_id"] | ||||
|             } | ||||
|  | ||||
|           }, error => { | ||||
|             this.uploadStatus.splice(this.uploadStatus.indexOf(uploadStatusObject), 1) | ||||
|             this.completedFiles += 1 | ||||
|             status.updateProgress(FileStatusPhase.FAILED) | ||||
|             switch (error.status) { | ||||
|               case 400: { | ||||
|                 this.toastService.showInfo($localize`There was an error while uploading the document: ${error.error.document}`) | ||||
|                 status.message = error.error.document | ||||
|                 break; | ||||
|               } | ||||
|               default: { | ||||
|                 this.toastService.showInfo($localize`An error has occurred while uploading the document. Sorry!`) | ||||
|                 status.message = $localize`An error has occurred while uploading the document. Sorry!` | ||||
|                 break; | ||||
|               } | ||||
|             } | ||||
|  | ||||
|           }) | ||||
|         }); | ||||
|       } | ||||
|   | ||||
							
								
								
									
										11
									
								
								src-ui/src/app/data/websocket-consumer-status-message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src-ui/src/app/data/websocket-consumer-status-message.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| export interface WebsocketConsumerStatusMessage { | ||||
|  | ||||
|   filename?: string | ||||
|   task_id?: string | ||||
|   current_progress?: number | ||||
|   max_progress?: number | ||||
|   status?: string | ||||
|   message?: string | ||||
|   document_id: number | ||||
|  | ||||
| } | ||||
| @@ -1,13 +1,57 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { Subject } from 'rxjs'; | ||||
| import { WebsocketConsumerStatusMessage } from '../data/websocket-consumer-status-message'; | ||||
|  | ||||
| export enum FileStatusPhase { | ||||
|   STARTED = 0, | ||||
|   UPLOADING = 1, | ||||
|   PROCESSING = 2, | ||||
|   SUCCESS = 3, | ||||
|   FAILED = 4 | ||||
| } | ||||
|  | ||||
| export class FileStatus { | ||||
|  | ||||
|   filename: string | ||||
|  | ||||
|   taskId: string | ||||
|  | ||||
|   phase: FileStatusPhase = FileStatusPhase.STARTED   | ||||
|  | ||||
|   currentPhaseProgress: number | ||||
|  | ||||
|   currentPhaseMaxProgress: number | ||||
|  | ||||
|   message: string | ||||
|  | ||||
|   documentId: number | ||||
|  | ||||
|   getProgress(): number { | ||||
|     switch (this.phase) { | ||||
|       case FileStatusPhase.STARTED: | ||||
|         return 0.0 | ||||
|       case FileStatusPhase.UPLOADING: | ||||
|         return this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.2 | ||||
|       case FileStatusPhase.PROCESSING: | ||||
|         return this.currentPhaseProgress / this.currentPhaseMaxProgress * 0.8 + 0.2 | ||||
|       case FileStatusPhase.SUCCESS: | ||||
|       case FileStatusPhase.FAILED: | ||||
|         return 1.0 | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   updateProgress(status: FileStatusPhase, currentProgress?: number, maxProgress?: number) { | ||||
|     if (status >= this.phase) { | ||||
|       this.phase = status | ||||
|       if (currentProgress) { | ||||
|         this.currentPhaseProgress = currentProgress | ||||
|       } | ||||
|       if (maxProgress) { | ||||
|         this.currentPhaseMaxProgress = maxProgress | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
| export interface FileStatus { | ||||
|   filename?: string | ||||
|   current_progress?: number | ||||
|   max_progress?: number | ||||
|   status?: string | ||||
|   message?: string | ||||
|   document_id?: number | ||||
| } | ||||
|  | ||||
| @Injectable({ | ||||
| @@ -23,24 +67,41 @@ export class ConsumerStatusService { | ||||
|   private documentConsumptionFinishedSubject = new Subject<FileStatus>() | ||||
|   private documentConsumptionFailedSubject = new Subject<FileStatus>() | ||||
|  | ||||
|   private get(taskId: string, filename?: string) { | ||||
|     let status = this.consumerStatus.find(e => e.taskId == taskId) || this.consumerStatus.find(e => e.filename == filename) | ||||
|     if (!status) { | ||||
|       status = new FileStatus() | ||||
|       this.consumerStatus.push(status) | ||||
|     } | ||||
|     status.taskId = taskId | ||||
|     status.filename = filename | ||||
|     return status | ||||
|   } | ||||
|  | ||||
|   newFileUpload(): FileStatus { | ||||
|     let status = new FileStatus() | ||||
|     this.consumerStatus.push(status) | ||||
|     return status | ||||
|   } | ||||
|  | ||||
|   connect() { | ||||
|     this.disconnect() | ||||
|     this.statusWebSocked = new WebSocket("ws://localhost:8000/ws/status/"); | ||||
|     this.statusWebSocked.onmessage = (ev) => { | ||||
|       let statusUpdate: FileStatus = JSON.parse(ev['data']) | ||||
|       let statusMessage: WebsocketConsumerStatusMessage = JSON.parse(ev['data']) | ||||
|  | ||||
|       let index = this.consumerStatus.findIndex(fs => fs.filename == statusUpdate.filename) | ||||
|       if (index > -1) { | ||||
|         this.consumerStatus[index] = statusUpdate | ||||
|       } else { | ||||
|         this.consumerStatus.push(statusUpdate) | ||||
|       } | ||||
|       let status = this.get(statusMessage.task_id, statusMessage.filename) | ||||
|       status.updateProgress(FileStatusPhase.PROCESSING, statusMessage.current_progress, statusMessage.max_progress) | ||||
|       status.message = statusMessage.message | ||||
|       status.documentId = statusMessage.document_id | ||||
|  | ||||
|       if (statusUpdate.status == "SUCCESS") { | ||||
|         this.documentConsumptionFinishedSubject.next(statusUpdate) | ||||
|       if (statusMessage.status == "SUCCESS") { | ||||
|         status.phase = FileStatusPhase.SUCCESS | ||||
|         this.documentConsumptionFinishedSubject.next(status) | ||||
|       } | ||||
|       if (statusUpdate.status == "FAILED") { | ||||
|         this.documentConsumptionFailedSubject.next(statusUpdate) | ||||
|       if (statusMessage.status == "FAILED") { | ||||
|         status.phase = FileStatusPhase.FAILED | ||||
|         this.documentConsumptionFailedSubject.next(status) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 jonaswinkler
					jonaswinkler