mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Re-implement file type progress bar
This commit is contained in:
		| @@ -2237,18 +2237,11 @@ | |||||||
|           <context context-type="linenumber">13</context> |           <context context-type="linenumber">13</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="6125391559813574136" datatype="html"> |       <trans-unit id="8693603235657020323" datatype="html"> | ||||||
|         <source>File types</source> |         <source>Other</source> | ||||||
|         <context-group purpose="location"> |  | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> |  | ||||||
|           <context context-type="linenumber">17</context> |  | ||||||
|         </context-group> |  | ||||||
|       </trans-unit> |  | ||||||
|       <trans-unit id="3881818169480672345" datatype="html"> |  | ||||||
|         <source>other</source> |  | ||||||
|         <context-group purpose="location"> |         <context-group purpose="location"> | ||||||
|           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts</context> |           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts</context> | ||||||
|           <context context-type="linenumber">56</context> |           <context context-type="linenumber">87</context> | ||||||
|         </context-group> |         </context-group> | ||||||
|       </trans-unit> |       </trans-unit> | ||||||
|       <trans-unit id="8187573012244728580" datatype="html"> |       <trans-unit id="8187573012244728580" datatype="html"> | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								src-ui/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -22,6 +22,7 @@ | |||||||
|         "@popperjs/core": "^2.11.6", |         "@popperjs/core": "^2.11.6", | ||||||
|         "bootstrap": "^5.2.3", |         "bootstrap": "^5.2.3", | ||||||
|         "file-saver": "^2.0.5", |         "file-saver": "^2.0.5", | ||||||
|  |         "mime-names": "^1.0.0", | ||||||
|         "ng2-pdf-viewer": "^9.1.2", |         "ng2-pdf-viewer": "^9.1.2", | ||||||
|         "ngx-color": "^8.0.3", |         "ngx-color": "^8.0.3", | ||||||
|         "ngx-cookie-service": "^15.0.0", |         "ngx-cookie-service": "^15.0.0", | ||||||
| @@ -13766,6 +13767,11 @@ | |||||||
|         "node": ">= 0.6" |         "node": ">= 0.6" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/mime-names": { | ||||||
|  |       "version": "1.0.0", | ||||||
|  |       "resolved": "https://registry.npmjs.org/mime-names/-/mime-names-1.0.0.tgz", | ||||||
|  |       "integrity": "sha512-vLNEfYU63fz34panv/L3Lh3eW3+v0BlOB+bSGFdntv/gBNnokCbSsaNuHR9vH/NS5oWbL0HqMQf/3we4fRJyIQ==" | ||||||
|  |     }, | ||||||
|     "node_modules/mime-types": { |     "node_modules/mime-types": { | ||||||
|       "version": "2.1.35", |       "version": "2.1.35", | ||||||
|       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", |       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ | |||||||
|     "@popperjs/core": "^2.11.6", |     "@popperjs/core": "^2.11.6", | ||||||
|     "bootstrap": "^5.2.3", |     "bootstrap": "^5.2.3", | ||||||
|     "file-saver": "^2.0.5", |     "file-saver": "^2.0.5", | ||||||
|  |     "mime-names": "^1.0.0", | ||||||
|     "ng2-pdf-viewer": "^9.1.2", |     "ng2-pdf-viewer": "^9.1.2", | ||||||
|     "ngx-color": "^8.0.3", |     "ngx-color": "^8.0.3", | ||||||
|     "ngx-cookie-service": "^15.0.0", |     "ngx-cookie-service": "^15.0.0", | ||||||
|   | |||||||
| @@ -13,33 +13,33 @@ | |||||||
|         <ng-container i18n>Total characters</ng-container>: |         <ng-container i18n>Total characters</ng-container>: | ||||||
|         <span class="badge bg-secondary text-light rounded-pill">{{statistics?.character_count | number}}</span> |         <span class="badge bg-secondary text-light rounded-pill">{{statistics?.character_count | number}}</span> | ||||||
|       </div> |       </div> | ||||||
|  |       <div *ngIf="statistics?.document_file_type_counts?.length > 1" class="list-group-item filetypes"> | ||||||
|       <div class="list-group-item widget-container"> |         <div class="d-flex justify-content-between align-items-center my-2"> | ||||||
|         <div class="file-type-bar"> |           <div class="progress flex-grow-1"> | ||||||
|           <ng-container |             <div *ngFor="let filetype of statistics?.document_file_type_counts; let i = index; let last = last" | ||||||
|             *ngFor=" |               class="progress-bar bg-primary text-primary-contrast" | ||||||
|               let fileType of fileTypeDataArray; |               role="progressbar" | ||||||
|               let isFirst = first; |               [ngbPopover]="getFileTypeName(filetype)" | ||||||
|               let isLast = last |               i18n-ngbPopover | ||||||
|             " |               triggers="mouseenter:mouseleave" | ||||||
|           > |               [attr.aria-label]="getFileTypeName(filetype)" | ||||||
|             <div |               [class.me-1px]="!last" | ||||||
|               class="file-type" |               [style.width]="getFileTypePercent(filetype) + '%'" | ||||||
|               [style.width.%]="fileType.percentage" |               [style.opacity]="getItemOpacity(i)" | ||||||
|               [style.backgroundColor]="fileType.color" |               [attr.aria-valuenow]="getFileTypePercent(filetype)" | ||||||
|               [ngClass]="{ 'rounded-left': isFirst, 'rounded-right': isLast }" |               aria-valuemin="0" | ||||||
|             ></div> |               aria-valuemax="100"> | ||||||
|           </ng-container> |             </div> | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|  |         <div class="d-flex flex-wrap align-items-start"> | ||||||
|  |           <div class="d-flex" *ngFor="let filetype of statistics?.document_file_type_counts; let i = index"> | ||||||
|  |             <div class="text-nowrap me-2"> | ||||||
|  |               <span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span> | ||||||
|  |               <small class="text-nowrap"><span class="fw-bold">{{ getFileTypeExtension(filetype) }}</span> <span class="text-muted">({{getFileTypePercent(filetype) | number: '1.0-1'}}%)</span></small> | ||||||
|  |             </div> | ||||||
|           </div> |           </div> | ||||||
|         <ng-container *ngFor="let fileType of fileTypeDataArray"> |  | ||||||
|           <div class="file-type-label"> |  | ||||||
|             <div |  | ||||||
|               class="file-type-color" |  | ||||||
|               [style.backgroundColor]="fileType.color" |  | ||||||
|             ></div> |  | ||||||
|             <span>{{ fileType.name }} ({{ fileType.percentage }}%)</span> |  | ||||||
|         </div> |         </div> | ||||||
|         </ng-container> |  | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|   </ng-container> |   </ng-container> | ||||||
|   | |||||||
| @@ -1,26 +1,10 @@ | |||||||
| .file-type-bar { | .filetypes { | ||||||
|   display: flex; |     .progress { | ||||||
|   height: 10px; |         height: 0.6rem; | ||||||
|   margin-bottom: 10px; |  | ||||||
|     } |     } | ||||||
| .file-type { |  | ||||||
|   height: 100%; |     .badge { | ||||||
|  |         height: 0.6rem; | ||||||
|  |         width: 0.6rem; | ||||||
|     } |     } | ||||||
| .file-type-label { |  | ||||||
|   align-items: center; |  | ||||||
|   float: left; |  | ||||||
|   padding-right: 10px; |  | ||||||
| } |  | ||||||
| .file-type-color { |  | ||||||
|   width: 10px; |  | ||||||
|   height: 10px; |  | ||||||
|   border-radius: 50%; |  | ||||||
|   display: inline-block; |  | ||||||
|   margin-right: 5px; |  | ||||||
| } |  | ||||||
| .rounded-left { |  | ||||||
|   border-radius: 5px 0 0 5px; |  | ||||||
| } |  | ||||||
| .rounded-right { |  | ||||||
|   border-radius: 0 5px 5px 0; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,11 @@ | |||||||
| import { HttpClient } from '@angular/common/http' | import { HttpClient } from '@angular/common/http' | ||||||
| import { Component, OnDestroy, OnInit } from '@angular/core' | import { Component, OnDestroy, OnInit } from '@angular/core' | ||||||
| import { Observable, Subscription } from 'rxjs' | import { Observable, Subscription } from 'rxjs' | ||||||
| import { | import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type' | ||||||
|   FILTER_HAS_TAGS_ALL, |  | ||||||
|   FILTER_IS_IN_INBOX, |  | ||||||
| } from 'src/app/data/filter-rule-type' |  | ||||||
| import { ConsumerStatusService } from 'src/app/services/consumer-status.service' | import { ConsumerStatusService } from 'src/app/services/consumer-status.service' | ||||||
| import { DocumentListViewService } from 'src/app/services/document-list-view.service' | import { DocumentListViewService } from 'src/app/services/document-list-view.service' | ||||||
| import { environment } from 'src/environments/environment' | import { environment } from 'src/environments/environment' | ||||||
|  | import * as mimeTypeNames from 'mime-names' | ||||||
|  |  | ||||||
| export interface Statistics { | export interface Statistics { | ||||||
|   documents_total?: number |   documents_total?: number | ||||||
| @@ -44,28 +42,17 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy { | |||||||
|     return this.http.get(`${environment.apiBaseUrl}statistics/`) |     return this.http.get(`${environment.apiBaseUrl}statistics/`) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   fileTypeDataArray = [] |  | ||||||
|  |  | ||||||
|   private fileTypeColors = [ |  | ||||||
|     '#e84118', // red |  | ||||||
|     '#00a8ff', // blue |  | ||||||
|     '#4cd137', // green |  | ||||||
|     '#9c88ff', // purple |  | ||||||
|     '#fbc531', // yellow |  | ||||||
|     '#7f8fa6', // gray |  | ||||||
|   ] |  | ||||||
|  |  | ||||||
|   reload() { |   reload() { | ||||||
|     this.loading = true |     this.loading = true | ||||||
|     this.getStatistics().subscribe((statistics) => { |     this.getStatistics().subscribe((statistics) => { | ||||||
|       this.loading = false |       this.loading = false | ||||||
|       const fileTypeMax = 5 |       const fileTypeMax = 5 | ||||||
|       if (statistics.document_file_type_counts?.length > fileTypeMax) { |       if (statistics.document_file_type_counts?.length > fileTypeMax) { | ||||||
|         let others = statistics.document_file_type_counts.slice(fileTypeMax) |         const others = statistics.document_file_type_counts.slice(fileTypeMax) | ||||||
|         statistics.document_file_type_counts = |         statistics.document_file_type_counts = | ||||||
|           statistics.document_file_type_counts.slice(0, fileTypeMax) |           statistics.document_file_type_counts.slice(0, fileTypeMax) | ||||||
|         statistics.document_file_type_counts.push({ |         statistics.document_file_type_counts.push({ | ||||||
|           mime_type: $localize`other`, |           mime_type: $localize`Other`, | ||||||
|           mime_type_count: others.reduce( |           mime_type_count: others.reduce( | ||||||
|             (currentValue, documentFileType) => |             (currentValue, documentFileType) => | ||||||
|               documentFileType.mime_type_count + currentValue, |               documentFileType.mime_type_count + currentValue, | ||||||
| @@ -74,26 +61,28 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy { | |||||||
|         }) |         }) | ||||||
|       } |       } | ||||||
|       this.statistics = statistics |       this.statistics = statistics | ||||||
|  |  | ||||||
|       this.updateFileTypePercentages() |  | ||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   private updateFileTypePercentages() { |   getFileTypeExtension(filetype: DocumentFileType): string { | ||||||
|     let colorIndex = 0 |     return ( | ||||||
|     this.fileTypeDataArray = this.statistics.document_file_type_counts.map( |       mimeTypeNames[filetype.mime_type]?.extensions[0]?.toUpperCase() ?? | ||||||
|       (fileType) => { |       filetype.mime_type | ||||||
|         const percentage = |  | ||||||
|           (fileType.mime_type_count / this.statistics?.documents_total) * 100 |  | ||||||
|         return { |  | ||||||
|           name: this.getMimeTypeName(fileType.mime_type), |  | ||||||
|           percentage: percentage.toFixed(2), |  | ||||||
|           color: this.fileTypeColors[colorIndex++], |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   getFileTypeName(filetype: DocumentFileType): string { | ||||||
|  |     return mimeTypeNames[filetype.mime_type]?.name ?? filetype.mime_type | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getFileTypePercent(filetype: DocumentFileType): number { | ||||||
|  |     return (filetype.mime_type_count / this.statistics?.documents_total) * 100 | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getItemOpacity(i: number): number { | ||||||
|  |     return 1 - i / this.statistics?.document_file_type_counts.length | ||||||
|  |   } | ||||||
|  |  | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|     this.reload() |     this.reload() | ||||||
|     this.subscription = this.consumerStatusService |     this.subscription = this.consumerStatusService | ||||||
| @@ -115,34 +104,4 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy { | |||||||
|       }, |       }, | ||||||
|     ]) |     ]) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getMimeTypeName(mimeType: string): string { |  | ||||||
|     const mimeTypesMap: { [key: string]: string } = { |  | ||||||
|       'application/msword': 'Microsoft Word', |  | ||||||
|       'application/vnd.openxmlformats-officedocument.wordprocessingml.document': |  | ||||||
|         'Microsoft Word', |  | ||||||
|       'application/vnd.ms-excel': 'Microsoft Excel', |  | ||||||
|       'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': |  | ||||||
|         'Microsoft Excel', |  | ||||||
|       'application/vnd.ms-powerpoint': 'Microsoft PowerPoint', |  | ||||||
|       'application/vnd.openxmlformats-officedocument.presentationml.presentation': |  | ||||||
|         'Microsoft PowerPoint', |  | ||||||
|       'application/pdf': 'PDF', |  | ||||||
|       'application/vnd.oasis.opendocument.text': 'OpenDocument Text', |  | ||||||
|       'application/vnd.oasis.opendocument.spreadsheet': |  | ||||||
|         'OpenDocument Spreadsheet', |  | ||||||
|       'application/vnd.oasis.opendocument.presentation': |  | ||||||
|         'OpenDocument Presentation', |  | ||||||
|       'application/vnd.oasis.opendocument.graphics': 'OpenDocument Graphics', |  | ||||||
|       'application/rtf': 'Rich Text Format', |  | ||||||
|       'text/plain': 'Plain Text', |  | ||||||
|       'text/csv': 'CSV', |  | ||||||
|       'image/jpeg': 'JPEG', |  | ||||||
|       'image/png': 'PNG', |  | ||||||
|       'image/gif': 'GIF', |  | ||||||
|       'image/svg+xml': 'SVG', |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return mimeTypesMap[mimeType] || mimeType |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -623,3 +623,7 @@ code { | |||||||
| .accordion-button::after { | .accordion-button::after { | ||||||
|   filter: invert(0.5) saturate(0); |   filter: invert(0.5) saturate(0); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .me-1px { | ||||||
|  |   margin-right: 1px !important; | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 shamoon
					shamoon