mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-19 10:19:27 -05:00
Merge pull request #1886 from paperless-ngx/1.9.2-ui-tweaks
Feature: 1.9.2 UI tweaks
This commit is contained in:
commit
0d941bfb05
@ -24,6 +24,7 @@ import { CorrespondentEditDialogComponent } from './components/common/edit-dialo
|
|||||||
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
import { TagEditDialogComponent } from './components/common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
|
||||||
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
import { DocumentTypeEditDialogComponent } from './components/common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
|
||||||
import { TagComponent } from './components/common/tag/tag.component'
|
import { TagComponent } from './components/common/tag/tag.component'
|
||||||
|
import { ClearableBadge } from './components/common/clearable-badge/clearable-badge.component'
|
||||||
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
|
import { PageHeaderComponent } from './components/common/page-header/page-header.component'
|
||||||
import { AppFrameComponent } from './components/app-frame/app-frame.component'
|
import { AppFrameComponent } from './components/app-frame/app-frame.component'
|
||||||
import { ToastsComponent } from './components/common/toasts/toasts.component'
|
import { ToastsComponent } from './components/common/toasts/toasts.component'
|
||||||
@ -142,6 +143,7 @@ function initializeApp(settings: SettingsService) {
|
|||||||
DocumentTypeEditDialogComponent,
|
DocumentTypeEditDialogComponent,
|
||||||
StoragePathEditDialogComponent,
|
StoragePathEditDialogComponent,
|
||||||
TagComponent,
|
TagComponent,
|
||||||
|
ClearableBadge,
|
||||||
PageHeaderComponent,
|
PageHeaderComponent,
|
||||||
AppFrameComponent,
|
AppFrameComponent,
|
||||||
ToastsComponent,
|
ToastsComponent,
|
||||||
|
@ -16,7 +16,12 @@
|
|||||||
<use xlink:href="assets/bootstrap-icons.svg#search"/>
|
<use xlink:href="assets/bootstrap-icons.svg#search"/>
|
||||||
</svg>
|
</svg>
|
||||||
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
|
<input class="form-control form-control-sm" type="text" placeholder="Search documents" aria-label="Search"
|
||||||
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (selectItem)="itemSelected($event)" i18n-placeholder>
|
[formControl]="searchField" [ngbTypeahead]="searchAutoComplete" (keyup)="searchFieldKeyup($event)" (selectItem)="itemSelected($event)" i18n-placeholder>
|
||||||
|
<button *ngIf="!searchFieldEmpty" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0" (click)="resetSearchField()">
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<ul ngbNav class="order-sm-3">
|
<ul ngbNav class="order-sm-3">
|
||||||
|
@ -243,17 +243,18 @@ main {
|
|||||||
|
|
||||||
form {
|
form {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
> svg {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0.6rem;
|
left: 0.6rem;
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
color: rgba(255, 255, 255, 0.6);
|
color: rgba(255, 255, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
svg {
|
form > svg {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +93,20 @@ export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
|
|||||||
|
|
||||||
searchField = new FormControl('')
|
searchField = new FormControl('')
|
||||||
|
|
||||||
|
get searchFieldEmpty(): boolean {
|
||||||
|
return this.searchField.value.trim().length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSearchField() {
|
||||||
|
this.searchField.reset('')
|
||||||
|
}
|
||||||
|
|
||||||
|
searchFieldKeyup(event: KeyboardEvent) {
|
||||||
|
if (event.key == 'Escape') {
|
||||||
|
this.resetSearchField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get openDocuments(): PaperlessDocument[] {
|
get openDocuments(): PaperlessDocument[] {
|
||||||
return this.openDocumentsService.getOpenDocuments()
|
return this.openDocumentsService.getOpenDocuments()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
<button *ngIf="active" class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light rounded-pill p-1" title="Clear" i18n-title (click)="onClick($event)">
|
||||||
|
<svg *ngIf="!isNumbered && selected" width="1em" height="1em" class="check m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#check-lg"/>
|
||||||
|
</svg>
|
||||||
|
<div *ngIf="isNumbered" class="number">{{number}}<span class="visually-hidden">selected</span></div>
|
||||||
|
<svg width=".9em" height="1em" class="x m-0 p-0 opacity-75" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<use xlink:href="assets/bootstrap-icons.svg#x-lg"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
@ -0,0 +1,28 @@
|
|||||||
|
.badge {
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
min-width: 1em;
|
||||||
|
min-height: 1em;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
.check,
|
||||||
|
.number {
|
||||||
|
opacity: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.x {
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: calc(50% - 4px);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-clearable-badge',
|
||||||
|
templateUrl: './clearable-badge.component.html',
|
||||||
|
styleUrls: ['./clearable-badge.component.scss'],
|
||||||
|
})
|
||||||
|
export class ClearableBadge {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
number: number
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
selected: boolean
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
cleared: EventEmitter<boolean> = new EventEmitter()
|
||||||
|
|
||||||
|
get active(): boolean {
|
||||||
|
return this.selected || this.number > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
get isNumbered(): boolean {
|
||||||
|
return this.number > -1
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(event: PointerEvent) {
|
||||||
|
this.cleared.emit(true)
|
||||||
|
event.stopImmediatePropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
<div class="btn-group w-100" ngbDropdown role="group">
|
<div class="btn-group w-100" ngbDropdown role="group">
|
||||||
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
|
<button class="btn btn-sm" id="dropdown{{title}}" ngbDropdownToggle [ngClass]="dateBefore || dateAfter ? 'btn-primary' : 'btn-outline-primary'">
|
||||||
{{title}}
|
{{title}}
|
||||||
<div *ngIf="isActive" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
<app-clearable-badge [selected]="isActive" (cleared)="reset()"></app-clearable-badge><span class="visually-hidden">selected</span>
|
||||||
<span class="visually-hidden">selected</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
<div class="dropdown-menu date-dropdown shadow pt-0" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
|
@ -106,6 +106,13 @@ export class DateDropdownComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.dateBefore = null
|
||||||
|
this.dateAfter = null
|
||||||
|
this.relativeDate = null
|
||||||
|
this.onChange()
|
||||||
|
}
|
||||||
|
|
||||||
setRelativeDate(rd: RelativeDate) {
|
setRelativeDate(rd: RelativeDate) {
|
||||||
this.dateBefore = null
|
this.dateBefore = null
|
||||||
this.dateAfter = null
|
this.dateAfter = null
|
||||||
|
@ -5,12 +5,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||||
<ng-container *ngIf="!editing && selectionModel.selectionSize() > 0">
|
<ng-container *ngIf="!editing && selectionModel.selectionSize() > 0">
|
||||||
<div *ngIf="multiple" class="position-absolute top-0 start-100 translate-middle badge bg-secondary border border-light text-light rounded-pill">
|
<app-clearable-badge [number]="multiple ? selectionModel.totalCount : undefined" [selected]="!multiple && selectionModel.selectionSize() > 0" (cleared)="reset()"></app-clearable-badge>
|
||||||
{{selectionModel.totalCount}}<span class="visually-hidden">selected</span>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!multiple" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
|
|
||||||
<span class="visually-hidden">selected</span>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
<div class="dropdown-menu py-0 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown{{title}}">
|
||||||
|
@ -384,4 +384,9 @@ export class FilterableDropdownComponent {
|
|||||||
this.selectionModel.exclude(itemID)
|
this.selectionModel.exclude(itemID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.selectionModel.reset()
|
||||||
|
this.selectionModelChange.emit(this.selectionModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,12 @@
|
|||||||
<select *ngIf="textFilterTarget == 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
|
<select *ngIf="textFilterTarget == 'asn'" class="form-select flex-grow-0 w-auto" [(ngModel)]="textFilterModifier" (change)="textFilterModifierChange()">
|
||||||
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
|
<option *ngFor="let m of textFilterModifiers" ngbDropdownItem [value]="m.id">{{m.label}}</option>
|
||||||
</select>
|
</select>
|
||||||
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup.enter)="textFilterEnter()" [readonly]="textFilterTarget == 'fulltext-morelike'">
|
<button *ngIf="_textFilter" class="btn btn-link btn-sm px-0 position-absolute top-0 end-0 z-10" (click)="resetTextField()">
|
||||||
|
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-x me-1" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<input #textFilterInput class="form-control form-control-sm" type="text" [disabled]="textFilterModifierIsNull" [(ngModel)]="textFilter" (keyup)="textFilterKeyup($event)" [readonly]="textFilterTarget == 'fulltext-morelike'">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,3 +21,7 @@
|
|||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.z-10 {
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
@ -709,13 +709,21 @@ export class FilterEditorComponent implements OnInit, OnDestroy {
|
|||||||
this.updateRules()
|
this.updateRules()
|
||||||
}
|
}
|
||||||
|
|
||||||
textFilterEnter() {
|
textFilterKeyup(event: KeyboardEvent) {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
const filterString = (
|
const filterString = (
|
||||||
this.textFilterInput.nativeElement as HTMLInputElement
|
this.textFilterInput.nativeElement as HTMLInputElement
|
||||||
).value
|
).value
|
||||||
if (filterString.length) {
|
if (filterString.length) {
|
||||||
this.updateTextFilter(filterString)
|
this.updateTextFilter(filterString)
|
||||||
}
|
}
|
||||||
|
} else if (event.key == 'Escape') {
|
||||||
|
this.resetTextField()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resetTextField() {
|
||||||
|
this.updateTextFilter('')
|
||||||
}
|
}
|
||||||
|
|
||||||
changeTextFilterTarget(target) {
|
changeTextFilterTarget(target) {
|
||||||
|
@ -587,7 +587,7 @@ a.badge {
|
|||||||
border-radius: 0.15rem;
|
border-radius: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .btn:not(:first-child) {
|
> .btn:not(:first-child):not(:nth-child(2)) {
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user