mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-09 09:58:20 -05:00
Re-implement file type progress bar
This commit is contained in:
parent
4d26a3d2c6
commit
0a977a9d0a
@ -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>
|
|
||||||
<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 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>
|
||||||
</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 {
|
.badge {
|
||||||
height: 100%;
|
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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user