mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Enhancement: relocate and smaller upload widget, dont limit upload list (#9244)
This commit is contained in:
@@ -49,12 +49,8 @@
|
||||
</div>
|
||||
<div class="col-12 col-lg-4 col-xl-3 col-sidebar">
|
||||
<div class="row row-cols-1 g-4 mb-4 sticky-lg-top z-0">
|
||||
<div class="col">
|
||||
<pngx-statistics-widget *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }"></pngx-statistics-widget>
|
||||
</div>
|
||||
<div class="col">
|
||||
<pngx-upload-file-widget></pngx-upload-file-widget>
|
||||
</div>
|
||||
<pngx-upload-file-widget></pngx-upload-file-widget>
|
||||
<pngx-statistics-widget *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }"></pngx-statistics-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,12 +1,15 @@
|
||||
<pngx-widget-frame title="Upload new documents" i18n-title *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }">
|
||||
<pngx-widget-frame *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }" [cardless]="true">
|
||||
<div content tourAnchor="tour.upload-widget">
|
||||
<form class="justify-content-center d-flex flex-column align-items-center py-3 px-2">
|
||||
<span class="text-muted" i18n>Drop documents anywhere or</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-3" (click)="fileUpload.click()" i18n>Browse files</button>
|
||||
<form class="justify-content-center d-flex flex-column align-items-center">
|
||||
<button type="button" class="btn btn-outline-dark bg-light shadow-sm w-100 h-100 pt-3 pb-2" (click)="fileUpload.click()">
|
||||
<i-bs class="text-primary" name="plus-circle"></i-bs>
|
||||
<span class="text-primary" i18n>Upload documents</span>
|
||||
<div class="text-muted smaller fst-italic" i18n>or drop files anywhere</div>
|
||||
</button>
|
||||
<input type="file" class="visually-hidden" (change)="onFileSelected($event)" multiple #fileUpload>
|
||||
</form>
|
||||
@if (getStatus().length > 0) {
|
||||
<div class="fixed-bottom p-2 p-md-4 d-flex justify-content-end pe-none max-vh100-40" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'offset-md-3 offset-lg-2'">
|
||||
<div class="fixed-bottom p-2 p-md-4 d-flex justify-content-end pe-none consumer-status-list" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'offset-md-3 offset-lg-2'">
|
||||
<div class="col col-lg-4 col-xl-3 ps-0 pe-0 ps-lg-3 pe-lg-0 pe-auto overflow-y-scroll">
|
||||
<div class="card shadow-sm consumer-status-card">
|
||||
<div class="card-body">
|
||||
@@ -30,24 +33,6 @@
|
||||
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
@if (getStatusHidden().length) {
|
||||
<div class="alerts-hidden">
|
||||
@if (!alertsExpanded) {
|
||||
<p class="mt-3 mb-0 text-center">
|
||||
<span i18n="This is shown as a summary line when there are more than 5 document in the processing pipeline.">{getStatusHidden().length, plural, =1 {One more document} other {{{getStatusHidden().length}} more documents}}</span>
|
||||
•
|
||||
<a [routerLink]="[]" (click)="alertsExpanded = !alertsExpanded" aria-controls="hiddenAlerts" [attr.aria-expanded]="alertsExpanded" i18n>Show all</a>
|
||||
</p>
|
||||
}
|
||||
<div #hiddenAlerts="ngbCollapse" [ngbCollapse]="!alertsExpanded" (ngbCollapseChange)="alertsExpanded = $event">
|
||||
@for (status of getStatusHidden(); track status) {
|
||||
<div>
|
||||
<ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -1,5 +1,13 @@
|
||||
form {
|
||||
position: relative;
|
||||
:host ::ng-deep i-bs svg {
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.btn-outline-dark {
|
||||
--bs-btn-border-color: var(--bs-border-color-translucent);
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.alert-heading {
|
||||
@@ -40,6 +48,10 @@ form {
|
||||
background-color: rgba(var(--bs-body-bg-rgb), .95) !important;
|
||||
}
|
||||
|
||||
.max-vh100-40 {
|
||||
max-height: calc(100vh - 40px);
|
||||
.consumer-status-list {
|
||||
max-height: calc(100vh - 315px); // e.g. below the upload button, mobile
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
max-height: calc(100vh - 200px); // e.g. below the upload button
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ import {
|
||||
} from '@angular/core/testing'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { NgbAlert, NgbCollapse } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||
import { routes } from 'src/app/app-routing.module'
|
||||
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
|
||||
@@ -116,20 +115,6 @@ describe('UploadFileWidgetComponent', () => {
|
||||
expect(component.getStatusColor(successStatus)).toEqual('success')
|
||||
})
|
||||
|
||||
it('should enforce a maximum number of alerts', () => {
|
||||
mockConsumerStatuses(websocketStatusService)
|
||||
fixture.detectChanges()
|
||||
// 5 total, 1 hidden
|
||||
expect(fixture.debugElement.queryAll(By.directive(NgbAlert))).toHaveLength(
|
||||
6
|
||||
)
|
||||
expect(
|
||||
fixture.debugElement
|
||||
.query(By.directive(NgbCollapse))
|
||||
.queryAll(By.directive(NgbAlert))
|
||||
).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should allow dismissing an alert', () => {
|
||||
const dismissSpy = jest.spyOn(websocketStatusService, 'dismiss')
|
||||
component.dismiss(new FileStatus())
|
||||
@@ -138,7 +123,6 @@ describe('UploadFileWidgetComponent', () => {
|
||||
|
||||
it('should allow dismissing completed alerts', fakeAsync(() => {
|
||||
mockConsumerStatuses(websocketStatusService)
|
||||
component.alertsExpanded = true
|
||||
fixture.detectChanges()
|
||||
jest
|
||||
.spyOn(component, 'getStatusCompleted')
|
||||
|
@@ -4,7 +4,6 @@ import { RouterModule } from '@angular/router'
|
||||
import {
|
||||
NgbAlert,
|
||||
NgbAlertModule,
|
||||
NgbCollapseModule,
|
||||
NgbProgressbarModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
@@ -21,8 +20,6 @@ import {
|
||||
} from 'src/app/services/websocket-status.service'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
|
||||
const MAX_ALERTS = 5
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-upload-file-widget',
|
||||
templateUrl: './upload-file-widget.component.html',
|
||||
@@ -34,15 +31,12 @@ const MAX_ALERTS = 5
|
||||
NgTemplateOutlet,
|
||||
RouterModule,
|
||||
NgbAlertModule,
|
||||
NgbCollapseModule,
|
||||
NgbProgressbarModule,
|
||||
NgxBootstrapIconsModule,
|
||||
TourNgBootstrapModule,
|
||||
],
|
||||
})
|
||||
export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||
alertsExpanded = false
|
||||
|
||||
@ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
|
||||
|
||||
constructor(
|
||||
@@ -54,7 +48,7 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
return this.websocketStatusService.getConsumerStatus().slice(0, MAX_ALERTS)
|
||||
return this.websocketStatusService.getConsumerStatus()
|
||||
}
|
||||
|
||||
getStatusSummary() {
|
||||
@@ -77,13 +71,6 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
|
||||
)
|
||||
}
|
||||
|
||||
getStatusHidden() {
|
||||
if (this.websocketStatusService.getConsumerStatus().length < MAX_ALERTS)
|
||||
return []
|
||||
else
|
||||
return this.websocketStatusService.getConsumerStatus().slice(MAX_ALERTS)
|
||||
}
|
||||
|
||||
getStatusUploading() {
|
||||
return this.websocketStatusService.getConsumerStatus(
|
||||
FileStatusPhase.UPLOADING
|
||||
|
@@ -1,23 +1,32 @@
|
||||
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
@if (draggable) {
|
||||
<div class="ms-n2 me-1" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
@if (!cardless) {
|
||||
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
@if (draggable) {
|
||||
<div class="ms-n2 me-1" cdkDragHandle>
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
}
|
||||
<h6 class="card-title mb-0">{{title}}</h6>
|
||||
</div>
|
||||
@if (loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
}
|
||||
<h6 class="card-title mb-0">{{title}}</h6>
|
||||
<ng-content select="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
@if (loading) {
|
||||
<div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
|
||||
<div class="visually-hidden" i18n>Loading...</div>
|
||||
}
|
||||
<ng-content select="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
<div class="card-body text-dark">
|
||||
<ng-container [ngTemplateOutlet]="content"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="fade" [class.show]="show">
|
||||
<ng-container [ngTemplateOutlet]="content"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="card-body text-dark">
|
||||
<ng-content select="[content]"></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #content>
|
||||
<ng-content select="[content]"></ng-content>
|
||||
</ng-template>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { NgTemplateOutlet } from '@angular/common'
|
||||
import { AfterViewInit, Component, Input } from '@angular/core'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { LoadingComponentWithPermissions } from 'src/app/components/loading-component/loading.component'
|
||||
@@ -7,7 +8,7 @@ import { LoadingComponentWithPermissions } from 'src/app/components/loading-comp
|
||||
selector: 'pngx-widget-frame',
|
||||
templateUrl: './widget-frame.component.html',
|
||||
styleUrls: ['./widget-frame.component.scss'],
|
||||
imports: [DragDropModule, NgxBootstrapIconsModule],
|
||||
imports: [DragDropModule, NgxBootstrapIconsModule, NgTemplateOutlet],
|
||||
})
|
||||
export class WidgetFrameComponent
|
||||
extends LoadingComponentWithPermissions
|
||||
@@ -26,6 +27,9 @@ export class WidgetFrameComponent
|
||||
@Input()
|
||||
draggable: any
|
||||
|
||||
@Input()
|
||||
cardless: boolean = false
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.show = true
|
||||
|
Reference in New Issue
Block a user