Customizable documents view

Fix
This commit is contained in:
shamoon 2024-04-18 23:45:04 -07:00
parent ffae52c9ac
commit eb1eab1dfc
15 changed files with 468 additions and 347 deletions

File diff suppressed because it is too large Load Diff

View File

@ -352,19 +352,17 @@
</div>
</div>
<div class="row">
@if (savedViewGroup.get(view.id.toString()).get('show_on_dashboard').value) {
<div class="col">
<pngx-input-number i18n-title title="Widget list limit" [showAdd]="false" formControlName="page_size"></pngx-input-number>
<pngx-input-number i18n-title title="Documents page size" [showAdd]="false" formControlName="page_size"></pngx-input-number>
</div>
<div class="col">
<label class="form-label" for="display_mode_{{view.id}}" i18n>Widget display</label>
<label class="form-label" for="display_mode_{{view.id}}" i18n>Display as</label>
<select class="form-select" formControlName="display_mode">
<option [ngValue]="DashboardViewMode.TABLE" i18n>Table</option>
<option [ngValue]="DashboardViewMode.SMALL_CARDS" i18n>Small Cards</option>
<option [ngValue]="DashboardViewMode.LARGE_CARDS" i18n>Large Cards</option>
</select>
</div>
}
@if (documentDisplayFields) {
<pngx-input-drag-drop-select i18n-title title="Show" [items]="documentDisplayFields" formControlName="document_display_fields"></pngx-input-drag-drop-select>
}

View File

@ -547,7 +547,6 @@ export class SettingsComponent
.subscribe({
next: () => {
this.store.next(this.settingsForm.value)
this.documentListViewService.updatePageSize()
this.settings.updateAppearanceSettings()
let savedToast: Toast = {
content: $localize`Settings were saved successfully.`,

View File

@ -14,7 +14,7 @@
<thead>
<tr>
@for (column of savedView.document_display_fields; track column; let i = $index) {
@if (activeDisplayFields.has(column)) {
@if (activeDisplayFields.includes(column)) {
<th
scope="col"
[ngClass]="{
@ -31,7 +31,7 @@
@for (doc of documents; track doc.id) {
<tr>
@for (column of savedView.document_display_fields; track column; let i = $index) {
@if (activeDisplayFields.has(column)) {
@if (activeDisplayFields.includes(column)) {
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': i > 1 }">
@switch (column) {
@case (DashboardViewTableColumn.ADDED) {

View File

@ -36,7 +36,6 @@ import {
} from 'src/app/services/permissions.service'
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
import { Results } from 'src/app/data/results'
@Component({
selector: 'pngx-saved-view-widget',
@ -81,11 +80,11 @@ export class SavedViewWidgetComponent
mouseOnPreview = false
popoverHidden = true
activeDisplayFields: Set<DocumentDisplayField> = new Set([
activeDisplayFields: DocumentDisplayField[] = [
DocumentDisplayField.TITLE,
DocumentDisplayField.CREATED,
DocumentDisplayField.ADDED,
])
]
ngOnInit(): void {
this.reload()
@ -121,7 +120,7 @@ export class SavedViewWidgetComponent
type &&
this.permissionsService.currentUserCan(PermissionAction.View, type)
)
this.activeDisplayFields.add(column)
this.activeDisplayFields.push(column)
})
}

View File

@ -15,7 +15,7 @@
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<h5 class="card-title">
@if (displayFields.has(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
@if (displayFields.includes(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
@if (clickCorrespondent.observers.length ) {
<a title="Filter by correspondent" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name}}</a>
} @else {
@ -23,10 +23,10 @@
}
:
}
@if (displayFields.has(DocumentDisplayField.TITLE)) {
@if (displayFields.includes(DocumentDisplayField.TITLE)) {
{{document.title | documentTitle}}
}
@if (displayFields.has(DocumentDisplayField.TAGS)) {
@if (displayFields.includes(DocumentDisplayField.TAGS)) {
@for (t of document.tags$ | async; track t) {
<pngx-tag [tag]="t" linkTitle="Filter by tag" i18n-linkTitle class="ms-1" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="clickTag.observers.length"></pngx-tag>
}
@ -34,7 +34,7 @@
</h5>
</div>
<p class="card-text">
@if (document.__search_hit__ && document.__search_hit__.highlights) {
@if (document.__search_hit__?.score && document.__search_hit__.highlights) {
<span [innerHtml]="document.__search_hit__.highlights"></span>
}
@for (highlight of searchNoteHighlights; track highlight) {
@ -43,7 +43,7 @@
<span [innerHtml]="highlight"></span>
</span>
}
@if (!document.__search_hit__) {
@if (!document.__search_hit__?.score) {
<span class="result-content">{{contentTrimmed}}</span>
}
</p>
@ -70,29 +70,29 @@
</div>
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
@if (displayFields.has(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
@if (displayFields.includes(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
<button routerLink="/documents/{{document.id}}/notes" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="View notes" i18n-title>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="chat-left-text"></i-bs><small i18n>{{document.notes.length}} Notes</small>
</button>
}
@if (displayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
@if (displayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by document type" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{(document.document_type$ | async)?.name}}</small>
</button>
}
@if (displayFields.has(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
@if (displayFields.includes(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
<button type="button" class="list-group-item btn btn-sm bg-light text-dark p-1 border-0 me-2 d-flex align-items-center" title="Filter by storage path" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{(document.storage_path$ | async)?.name}}</small>
</button>
}
@if (displayFields.has(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
@if (displayFields.includes(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
<div class="list-group-item me-2 bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document.archive_serial_number}}</small>
</div>
}
@if (displayFields.has(DocumentDisplayField.CREATED) || displayFields.has(DocumentDisplayField.ADDED)) {
@if (displayFields.includes(DocumentDisplayField.CREATED) || displayFields.includes(DocumentDisplayField.ADDED)) {
<ng-template #dateTooltip>
<div class="d-flex flex-column text-light">
<span i18n>Created: {{ document.created | customDate }}</span>
@ -100,19 +100,23 @@
<span i18n>Modified: {{ document.modified | customDate }}</span>
</div>
</ng-template>
@if (displayFields.includes(DocumentDisplayField.CREATED)) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created_date | customDate:'mediumDate'}} }
@else { {{document.added | customDate:'mediumDate'}} }
</small>
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
</div>
}
@if (displayFields.has(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
@if (displayFields.includes(DocumentDisplayField.ADDED)) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.added | customDate:'mediumDate'}}</small>
</div>
}
}
@if (displayFields.includes(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>
</div>
}
@if (displayFields.has(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
@if (displayFields.includes(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="people-fill"></i-bs><small i18n>Shared</small>
</div>
@ -124,7 +128,7 @@
</div>
}
@for (field of document.custom_fields; track field.id) {
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
@if (displayFields.includes(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center">
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="ui-radios"></i-bs>
<small>

View File

@ -35,9 +35,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
selected = false
@Input()
displayFields: Set<DocumentDisplayField | string> = new Set(
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
)
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
@Output()
toggleSelected = new EventEmitter()

View File

@ -10,7 +10,7 @@
</div>
</div>
@if (displayFields?.has(DocumentDisplayField.TAGS)) {
@if (displayFields?.includes(DocumentDisplayField.TAGS)) {
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
@for (t of getTagsLimited$() | async; track t) {
<pngx-tag [tag]="t" (click)="clickTag.emit(t.id);$event.stopPropagation()" [clickable]="true" linkTitle="Toggle tag filter" i18n-linkTitle></pngx-tag>
@ -24,7 +24,7 @@
}
</div>
@if (displayFields.has(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
@if (displayFields.includes(DocumentDisplayField.NOTES) && notesEnabled && document.notes.length) {
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
<span class="badge rounded-pill bg-light border text-primary">
<i-bs width="1.2em" height="1.2em" class="ms-1 me-1" name="chat-left-text"></i-bs>
@ -34,31 +34,31 @@
<div class="card-body bg-light p-2">
<p class="card-text">
@if (displayFields.has(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
@if (displayFields.includes(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
<a title="Toggle correspondent filter" i18n-title (click)="clickCorrespondent.emit(document.correspondent);$event.stopPropagation()" class="fw-bold btn-link">{{(document.correspondent$ | async)?.name ?? privateName}}</a>:
}
@if (displayFields.has(DocumentDisplayField.TITLE)) {
@if (displayFields.includes(DocumentDisplayField.TITLE)) {
{{document.title | documentTitle}}
}
</p>
</div>
<div class="card-footer pt-0 pb-2 px-2">
<div class="list-group list-group-flush border-0 pt-1 pb-2 card-info">
@if (displayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
@if (displayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && document.document_type) {
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle document type filter" i18n-title
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
</button>
}
@if (displayFields.has(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
@if (displayFields.includes(DocumentDisplayField.STORAGE_PATH) && document.storage_path) {
<button type="button" class="list-group-item list-group-item-action bg-transparent ps-0 p-1 border-0" title="Toggle storage path filter" i18n-title
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
</button>
}
@if (displayFields.has(DocumentDisplayField.CREATED) || displayFields.has(DocumentDisplayField.ADDED)) {
@if (displayFields.includes(DocumentDisplayField.CREATED) || displayFields.includes(DocumentDisplayField.ADDED)) {
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
<ng-template #dateTooltip>
<div class="d-flex flex-column text-light">
@ -70,32 +70,32 @@
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
<small>
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
@if (displayFields.includes(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
@else { {{document.added | customDate:'mediumDate'}} }
</small>
</div>
</div>
}
@if (displayFields.has(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
@if (displayFields.includes(DocumentDisplayField.ASN) && document.archive_serial_number | isNumber) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
<small>#{{document.archive_serial_number}}</small>
</div>
}
@if (displayFields.has(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
@if (displayFields.includes(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
<small>{{document.owner | username}}</small>
</div>
}
@if (displayFields.has(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
@if (displayFields.includes(DocumentDisplayField.SHARED) && document.is_shared_by_requester) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
<small i18n>Shared</small>
</div>
}
@for (field of document.custom_fields; track field.id) {
@if (displayFields.has(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
@if (displayFields.includes(DocumentDisplayField.CUSTOM_FIELD + field.field)) {
<div class="ps-0 p-1">
<i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
<pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>

View File

@ -42,9 +42,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
document: Document
@Input()
displayFields: Set<DocumentDisplayField | string> = new Set(
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
)
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
@Output()
dblClickDocument = new EventEmitter()

View File

@ -20,7 +20,7 @@
<div class="px-3">
@for (field of settingsService.allDocumentDisplayFields; track field.id) {
<div class="form-check small">
<input type="checkbox" class="form-check-input" id="displayField{{field.id}}" [checked]="activeDisplayFields.has(field.id)" (change)="toggleDisplayField(field.id)">
<input type="checkbox" class="form-check-input" id="displayField{{field.id}}" [checked]="activeDisplayFields.includes(field.id)" (change)="toggleDisplayField(field.id)">
<label class="form-check-label small" for="displayField{{field.id}}">{{field.name}}</label>
</div>
}
@ -28,15 +28,15 @@
</div>
</div>
<div class="btn-group flex-fill" role="group">
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="table" (ngModelChange)="saveDisplayMode()" id="displayModeDetails" name="displayModeDetails">
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="table" id="displayModeDetails" name="displayModeDetails">
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
<i-bs name="list-ul"></i-bs>
</label>
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="smallCards" (ngModelChange)="saveDisplayMode()" id="displayModeSmall" name="displayModeSmall">
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="smallCards" id="displayModeSmall" name="displayModeSmall">
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
<i-bs name="grid"></i-bs>
</label>
<input type="radio" class="btn-check" [(ngModel)]="displayMode" value="largeCards" (ngModelChange)="saveDisplayMode()" id="displayModeLarge" name="displayModeLarge">
<input type="radio" class="btn-check" [(ngModel)]="list.displayMode" value="largeCards" id="displayModeLarge" name="displayModeLarge">
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
<i-bs name="hdd-stack"></i-bs>
</label>
@ -57,7 +57,7 @@
</div>
<div>
@for (f of getSortFields(); track f) {
<button ngbDropdownItem (click)="setSortField(f.field)"
<button ngbDropdownItem (click)="list.sortField = f.field"
[class.active]="list.sortField === f.field">{{f.name}}
</button>
}
@ -125,7 +125,7 @@
}
</div>
@if (list.collectionSize) {
<ngb-pagination [pageSize]="list.currentPageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
<ngb-pagination [pageSize]="list.pageSize" [collectionSize]="list.collectionSize" [(page)]="list.currentPage" [maxSize]="5"
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
}
</div>
@ -138,7 +138,7 @@
@if (list.error ) {
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
} @else {
@if (displayMode === 'largeCards') {
@if (list.displayMode === DisplayMode.LARGE_CARDS) {
<div>
@for (d of list.documents; track trackByDocumentId($index, d)) {
<pngx-document-card-large
@ -156,11 +156,11 @@
}
</div>
}
@if (displayMode === 'details' || displayMode === 'table') {
@if (list.displayMode === DisplayMode.TABLE) {
<table class="table table-sm align-middle border shadow-sm">
<thead>
<th></th>
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
<th class="d-none d-lg-table-cell"
pngxSortable="archive_serial_number"
title="Sort by ASN" i18n-title
@ -169,7 +169,7 @@
(sort)="onSort($event)"
i18n>ASN</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
@if (activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
<th class="d-none d-md-table-cell"
pngxSortable="correspondent__name"
title="Sort by correspondent" i18n-title
@ -178,7 +178,7 @@
(sort)="onSort($event)"
i18n>Correspondent</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.TITLE)) {
@if (activeDisplayFields.includes(DocumentDisplayField.TITLE)) {
<th
pngxSortable="title"
title="Sort by title" i18n-title
@ -187,7 +187,7 @@
(sort)="onSort($event)"
i18n>Title</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.OWNER)) {
@if (activeDisplayFields.includes(DocumentDisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
<th class="d-none d-xl-table-cell"
pngxSortable="owner"
title="Sort by owner" i18n-title
@ -196,7 +196,7 @@
(sort)="onSort($event)"
i18n>Owner</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
<th class="d-none d-xl-table-cell"
pngxSortable="num_notes"
title="Sort by notes" i18n-title
@ -205,7 +205,7 @@
(sort)="onSort($event)"
i18n>Notes</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
@if (activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<th class="d-none d-xl-table-cell"
pngxSortable="document_type__name"
title="Sort by document type" i18n-title
@ -214,7 +214,7 @@
(sort)="onSort($event)"
i18n>Document type</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
@if (activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<th class="d-none d-xl-table-cell"
pngxSortable="storage_path__name"
title="Sort by storage path" i18n-title
@ -223,7 +223,7 @@
(sort)="onSort($event)"
i18n>Storage path</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
<th
pngxSortable="created"
title="Sort by created date" i18n-title
@ -232,7 +232,7 @@
(sort)="onSort($event)"
i18n>Created</th>
}
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
<th
pngxSortable="added"
title="Sort by added date" i18n-title
@ -256,12 +256,12 @@
<label class="form-check-label" for="docCheck{{d.id}}"></label>
</div>
</td>
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
<td class="d-none d-xl-table-cell">
{{d.archive_serial_number}}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
@if (activeDisplayFields.includes(DocumentDisplayField.CORRESPONDENT) && permissionService.currentUserCan(PermissionAction.View, PermissionType.Correspondent)) {
<td class="d-none d-xl-table-cell">
@if (d.correspondent) {
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
@ -269,21 +269,21 @@
</td>
}
<td>
@if (activeDisplayFields.has(DocumentDisplayField.TITLE)) {
@if (activeDisplayFields.includes(DocumentDisplayField.TITLE)) {
<a routerLink="/documents/{{d.id}}" title="Edit document" i18n-title style="overflow-wrap: anywhere;">{{d.title | documentTitle}}</a>
}
@if (activeDisplayFields.has(DocumentDisplayField.TAGS)) {
@if (activeDisplayFields.includes(DocumentDisplayField.TAGS)) {
@for (t of d.tags$ | async; track t) {
<pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
}
}
</td>
@if (activeDisplayFields.has(DocumentDisplayField.OWNER)) {
@if (activeDisplayFields.includes(DocumentDisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
<td>
{{d.owner | username}}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
<td class="d-none d-xl-table-cell">
@if (d.notes.length) {
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
@ -294,26 +294,26 @@
}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
@if (activeDisplayFields.includes(DocumentDisplayField.DOCUMENT_TYPE) && permissionService.currentUserCan(PermissionAction.View, PermissionType.DocumentType)) {
<td class="d-none d-xl-table-cell">
@if (d.document_type) {
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
@if (activeDisplayFields.includes(DocumentDisplayField.STORAGE_PATH) && permissionService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
<td class="d-none d-xl-table-cell">
@if (d.storage_path) {
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
<td>
{{d.created_date | customDate}}
</td>
}
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
<td>
{{d.added | customDate}}
</td>
@ -328,7 +328,7 @@
</tbody>
</table>
}
@if (displayMode === 'smallCards') {
@if (list.displayMode === DisplayMode.SMALL_CARDS) {
<div class="row row-cols-paperless-cards">
@for (d of list.documents; track trackByDocumentId($index, d)) {
<pngx-document-card-small class="p-0"

View File

@ -80,3 +80,7 @@ $paperless-card-breakpoints: (
a {
cursor: pointer;
}
pngx-page-header .dropdown-menu {
--bs-dropdown-min-width: 12em;
}

View File

@ -34,7 +34,12 @@ import {
import { Subject, of, throwError } from 'rxjs'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
import { DocumentDisplayField, SavedView } from 'src/app/data/saved-view'
import {
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
DisplayMode,
DocumentDisplayField,
SavedView,
} from 'src/app/data/saved-view'
import {
FILTER_FULLTEXT_MORELIKE,
FILTER_FULLTEXT_QUERY,
@ -169,17 +174,6 @@ describe('DocumentListComponent', () => {
component = fixture.componentInstance
})
it('should load display mode from local storage', () => {
window.localStorage.setItem('document-list:displayMode', 'largeCards')
fixture.detectChanges()
expect(component.displayMode).toEqual('largeCards')
component.displayMode = 'smallCards'
component.saveDisplayMode()
expect(window.localStorage.getItem('document-list:displayMode')).toEqual(
'smallCards'
)
})
it('should reload on new document consumed', () => {
const reloadSpy = jest.spyOn(documentListService, 'reload')
const fileStatusSubject = new Subject<FileStatus>()
@ -297,18 +291,18 @@ describe('DocumentListComponent', () => {
const displayModeButtons = fixture.debugElement.queryAll(
By.css('input[type="radio"]')
)
expect(component.displayMode).toEqual('smallCards')
expect(component.list.displayMode).toEqual('smallCards')
displayModeButtons[0].nativeElement.checked = true
displayModeButtons[0].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('table')
expect(component.list.displayMode).toEqual('table')
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
displayModeButtons[1].nativeElement.checked = true
displayModeButtons[1].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('smallCards')
expect(component.list.displayMode).toEqual('smallCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
).toHaveLength(3)
@ -316,7 +310,7 @@ describe('DocumentListComponent', () => {
displayModeButtons[2].nativeElement.checked = true
displayModeButtons[2].triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('largeCards')
expect(component.list.displayMode).toEqual('largeCards')
expect(
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
).toHaveLength(3)
@ -337,7 +331,7 @@ describe('DocumentListComponent', () => {
})
it('should support setting sort field by table head', () => {
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
component.activeDisplayFields = [DocumentDisplayField.ASN]
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
fixture.detectChanges()
expect(documentListService.sortField).toEqual('created')
@ -348,7 +342,7 @@ describe('DocumentListComponent', () => {
detailsDisplayModeButton.nativeElement.checked = true
detailsDisplayModeButton.triggerEventHandler('change')
fixture.detectChanges()
expect(component.displayMode).toEqual('table')
expect(component.list.displayMode).toEqual(DisplayMode.TABLE)
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
sortTh.triggerEventHandler('click')
@ -547,6 +541,42 @@ describe('DocumentListComponent', () => {
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
})
it('should detect saved view changes', () => {
const view: SavedView = {
id: 10,
name: 'Saved View 10',
sort_field: 'added',
sort_reverse: true,
filter_rules: [
{
rule_type: FILTER_HAS_TAGS_ANY,
value: '20',
},
],
page_size: 5,
display_mode: DisplayMode.SMALL_CARDS,
document_display_fields: [DocumentDisplayField.TITLE],
}
jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
const queryParams = { view: view.id.toString() }
jest
.spyOn(activatedRoute, 'queryParamMap', 'get')
.mockReturnValue(of(convertToParamMap(queryParams)))
activatedRoute.snapshot.queryParams = queryParams
router.routerState.snapshot.url = '/view/10/'
fixture.detectChanges()
expect(documentListService.activeSavedViewId).toEqual(10)
component.list.documentDisplayFields = [DocumentDisplayField.ASN]
expect(component.savedViewIsModified).toBeTruthy()
component.list.documentDisplayFields = [DocumentDisplayField.TITLE]
expect(component.savedViewIsModified).toBeFalsy()
component.list.displayMode = DisplayMode.TABLE
expect(component.savedViewIsModified).toBeTruthy()
component.list.displayMode = DisplayMode.SMALL_CARDS
expect(component.savedViewIsModified).toBeFalsy()
})
it('should navigate to a document', () => {
fixture.detectChanges()
const routerSpy = jest.spyOn(router, 'navigate')
@ -559,12 +589,15 @@ describe('DocumentListComponent', () => {
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
expect(documentListService.sortField).toEqual('created')
component.displayMode = 'table'
component.list.displayMode = DisplayMode.TABLE
component.list.documentDisplayFields = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map(
(f) => f.id
)
fixture.detectChanges()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(8)
).toHaveLength(9)
expect(component.notesEnabled).toBeTruthy()
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
@ -572,7 +605,7 @@ describe('DocumentListComponent', () => {
expect(component.notesEnabled).toBeFalsy()
expect(
fixture.debugElement.queryAll(By.directive(SortableDirective))
).toHaveLength(7)
).toHaveLength(8)
// insufficient perms
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
@ -600,30 +633,16 @@ describe('DocumentListComponent', () => {
])
})
it('should load display fields from local storage', () => {
window.localStorage.setItem('document-list:displayFields', '["asn"]')
fixture.detectChanges()
expect(component.activeDisplayFields).toEqual(
new Set([DocumentDisplayField.ASN])
)
component.activeDisplayFields = new Set([DocumentDisplayField.TITLE])
component.saveDisplayFields()
expect(
JSON.parse(window.localStorage.getItem('document-list:displayFields'))
).toEqual([DocumentDisplayField.TITLE])
})
it('should support toggling display fields', () => {
fixture.detectChanges()
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
component.activeDisplayFields = [DocumentDisplayField.ASN]
component.toggleDisplayField(DocumentDisplayField.TITLE)
expect(component.activeDisplayFields).toEqual(
new Set([DocumentDisplayField.ASN, DocumentDisplayField.TITLE])
)
expect(component.activeDisplayFields).toEqual([
DocumentDisplayField.ASN,
DocumentDisplayField.TITLE,
])
component.toggleDisplayField(DocumentDisplayField.ASN)
expect(component.activeDisplayFields).toEqual(
new Set([DocumentDisplayField.TITLE])
)
expect(component.activeDisplayFields).toEqual([DocumentDisplayField.TITLE])
})
it('should get custom field title', () => {

View File

@ -51,6 +51,7 @@ export class DocumentListComponent
implements OnInit, OnDestroy
{
DocumentDisplayField = DocumentDisplayField
DisplayMode = DisplayMode
constructor(
public list: DocumentListViewService,
@ -72,27 +73,12 @@ export class DocumentListComponent
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
displayMode: string = DisplayMode.SMALL_CARDS // largeCards, smallCards, table
_activeDisplayFields: Set<DocumentDisplayField | string> = new Set([
DocumentDisplayField.TITLE,
DocumentDisplayField.CORRESPONDENT,
DocumentDisplayField.CREATED,
DocumentDisplayField.TAGS,
DocumentDisplayField.DOCUMENT_TYPE,
DocumentDisplayField.STORAGE_PATH,
DocumentDisplayField.NOTES,
DocumentDisplayField.OWNER,
DocumentDisplayField.ASN,
DocumentDisplayField.SHARED,
])
get activeDisplayFields(): Set<DocumentDisplayField | string> {
return this._activeDisplayFields
get activeDisplayFields(): DocumentDisplayField[] {
return this.list.documentDisplayFields
}
set activeDisplayFields(fields: Set<DocumentDisplayField | string>) {
this._activeDisplayFields = fields
set activeDisplayFields(fields: DocumentDisplayField[]) {
this.list.documentDisplayFields = fields
this.updateDisplayCustomFields()
}
activeDisplayCustomFields: Set<string> = new Set()
@ -118,6 +104,11 @@ export class DocumentListComponent
return (
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
(this.unmodifiedSavedView.page_size &&
this.unmodifiedSavedView.page_size !== this.list.pageSize) ||
this.unmodifiedSavedView.display_mode !== this.list.displayMode ||
this.unmodifiedSavedView.document_display_fields.join(',') !==
this.activeDisplayFields.join(',') ||
filterRulesDiffer(
this.unmodifiedSavedView.filter_rules,
this.list.filterRules
@ -154,10 +145,6 @@ export class DocumentListComponent
return this.list.sortReverse
}
setSortField(field: string) {
this.list.sortField = field
}
onSort(event: SortEvent) {
this.list.setSort(event.column, event.reverse)
}
@ -166,25 +153,15 @@ export class DocumentListComponent
return this.list.selected.size > 0
}
saveDisplayMode() {
localStorage.setItem('document-list:displayMode', this.displayMode)
}
saveDisplayFields() {
localStorage.setItem(
'document-list:displayFields',
JSON.stringify(Array.from(this.activeDisplayFields))
toggleDisplayField(field: DocumentDisplayField) {
if (this.activeDisplayFields.includes(field)) {
this.activeDisplayFields = this.activeDisplayFields.filter(
(f) => f !== field
)
}
toggleDisplayField(field: string) {
if (this.activeDisplayFields.has(field)) {
this.activeDisplayFields.delete(field)
} else {
this.activeDisplayFields.add(field)
this.activeDisplayFields = [...this.activeDisplayFields, field]
}
this.updateDisplayCustomFields()
this.saveDisplayFields()
}
public getDisplayCustomFieldTitle(field: string) {
@ -194,16 +171,6 @@ export class DocumentListComponent
}
ngOnInit(): void {
if (localStorage.getItem('document-list:displayMode') != null) {
this.displayMode = localStorage.getItem('document-list:displayMode')
}
if (localStorage.getItem('document-list:displayFields') != null) {
this.activeDisplayFields = new Set(
JSON.parse(localStorage.getItem('document-list:displayFields'))
)
}
this.consumerStatusService
.onDocumentConsumptionFinished()
.pipe(takeUntil(this.unsubscribeNotifier))
@ -228,6 +195,12 @@ export class DocumentListComponent
})
return
}
if (!view.display_mode) {
view.display_mode = this.list.displayMode
}
if (!view.document_display_fields) {
view.document_display_fields = this.list.documentDisplayFields
}
this.unmodifiedSavedView = view
this.list.activateSavedViewWithQueryParams(
view,

View File

@ -12,13 +12,19 @@ import {
FILTER_HAS_TAGS_ALL,
FILTER_HAS_TAGS_ANY,
} from '../data/filter-rule-type'
import { SavedView } from '../data/saved-view'
import {
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
DisplayMode,
DocumentDisplayField,
SavedView,
} from '../data/saved-view'
import { FilterRule } from '../data/filter-rule'
import { RouterTestingModule } from '@angular/router/testing'
import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from '../guards/permissions.guard'
import { SettingsService } from './settings.service'
import { SETTINGS_KEYS } from '../data/ui-settings'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
const documents = [
{
@ -213,7 +219,7 @@ describe('DocumentListViewService', () => {
documentListViewService.loadFromQueryParams(convertToParamMap(params))
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${
documentListViewService.currentPageSize
documentListViewService.pageSize
}&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
)
expect(req.request.method).toEqual('GET')
@ -231,7 +237,7 @@ describe('DocumentListViewService', () => {
}
documentListViewService.loadFromQueryParams(convertToParamMap(params))
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
expect(documentListViewService.filterRules).toEqual([
@ -249,7 +255,7 @@ describe('DocumentListViewService', () => {
it('should use filter rules to update query params', () => {
documentListViewService.filterRules = filterRules
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
})
@ -257,7 +263,7 @@ describe('DocumentListViewService', () => {
it('should support quick filter', () => {
documentListViewService.quickFilter(filterRules)
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.currentPageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
`${environment.apiBaseUrl}documents/?page=${documentListViewService.currentPage}&page_size=${documentListViewService.pageSize}&ordering=-created&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
})
@ -280,7 +286,7 @@ describe('DocumentListViewService', () => {
convertToParamMap(params)
)
let req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.currentPageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${documentListViewService.pageSize}&ordering=-added&truncate_content=true&tags__id__all=${tags__id__all}`
)
expect(req.request.method).toEqual('GET')
// reset the list
@ -305,8 +311,7 @@ describe('DocumentListViewService', () => {
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
expect(documentListViewService.currentPage).toEqual(1)
documentListViewService.currentPageSize = 3
documentListViewService.reload()
documentListViewService.pageSize = 3
req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
@ -362,7 +367,10 @@ describe('DocumentListViewService', () => {
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
expect(documentListViewService.currentPage).toEqual(1)
documentListViewService.currentPageSize = 3
documentListViewService.pageSize = 3
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
jest
.spyOn(documentListViewService, 'getLastPage')
.mockReturnValue(Math.ceil(documents.length / 3))
@ -410,7 +418,13 @@ describe('DocumentListViewService', () => {
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
documentListViewService.currentPage = 2
documentListViewService.currentPageSize = 3
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=2&page_size=50&ordering=-created&truncate_content=true`
)
documentListViewService.pageSize = 3
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=2&page_size=3&ordering=-created&truncate_content=true`
)
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
documentListViewService.getPrevious(1).subscribe({
next: () => {},
@ -426,8 +440,7 @@ describe('DocumentListViewService', () => {
it('should update page size from settings', () => {
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
documentListViewService.updatePageSize()
expect(documentListViewService.currentPageSize).toEqual(10)
expect(documentListViewService.pageSize).toEqual(10)
})
it('should support select a document', () => {
@ -459,8 +472,7 @@ describe('DocumentListViewService', () => {
})
it('should support select page', () => {
documentListViewService.currentPageSize = 3
documentListViewService.reload()
documentListViewService.pageSize = 3
const req = httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
@ -544,4 +556,38 @@ describe('DocumentListViewService', () => {
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
})
it('should update default view state when display mode changes', () => {
const localStorageSpy = jest.spyOn(localStorage, 'setItem')
expect(documentListViewService.displayMode).toEqual(DisplayMode.SMALL_CARDS)
documentListViewService.displayMode = DisplayMode.LARGE_CARDS
expect(documentListViewService.displayMode).toEqual(DisplayMode.LARGE_CARDS)
documentListViewService.displayMode = 'details' as any // legacy
expect(documentListViewService.displayMode).toEqual(DisplayMode.TABLE)
expect(localStorageSpy).toHaveBeenCalledTimes(2)
})
it('should update default view state when display fields change', () => {
const localStorageSpy = jest.spyOn(localStorage, 'setItem')
documentListViewService.documentDisplayFields = [
DocumentDisplayField.ADDED,
DocumentDisplayField.TITLE,
]
expect(documentListViewService.documentDisplayFields).toEqual([
DocumentDisplayField.ADDED,
DocumentDisplayField.TITLE,
])
expect(localStorageSpy).toHaveBeenCalled()
// reload triggered
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
documentListViewService.documentDisplayFields = null
httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
expect(documentListViewService.documentDisplayFields).toEqual(
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
)
})
})

View File

@ -8,7 +8,12 @@ import {
isFullTextFilterRule,
} from '../utils/filter-rules'
import { Document } from '../data/document'
import { SavedView } from '../data/saved-view'
import {
DEFAULT_DOCUMENT_DISPLAY_FIELDS,
DisplayMode,
DocumentDisplayField,
SavedView,
} from '../data/saved-view'
import { SETTINGS_KEYS } from '../data/ui-settings'
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
@ -59,6 +64,21 @@ export interface ListViewState {
* Contains the IDs of all selected documents.
*/
selected?: Set<number>
/**
* The page size of the list view.
*/
pageSize?: number
/**
* Display mode of the list view.
*/
displayMode?: DisplayMode
/**
* The fields to display in the document list.
*/
documentDisplayFields?: DocumentDisplayField[]
}
/**
@ -80,8 +100,6 @@ export class DocumentListViewService {
selectionData?: SelectionData
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
private unsubscribeNotifier: Subject<any> = new Subject()
private listViewStates: Map<number, ListViewState> = new Map()
@ -113,7 +131,7 @@ export class DocumentListViewService {
delete savedState[k]
}
})
//only use restored state attributes instead of defaults if they are not null
// only use restored state attributes instead of defaults if they are not null
let newState = Object.assign(this.defaultListViewState(), savedState)
this.listViewStates.set(null, newState)
} catch (e) {
@ -176,6 +194,10 @@ export class DocumentListViewService {
if (this._activeSavedViewId) {
this.activeListViewState.title = view.name
}
this.activeListViewState.displayMode = view.display_mode
this.activeListViewState.pageSize = view.page_size
this.activeListViewState.documentDisplayFields =
view.document_display_fields
this.reduceSelectionToFilter()
@ -220,7 +242,7 @@ export class DocumentListViewService {
this.documentService
.listFiltered(
activeListViewState.currentPage,
this.currentPageSize,
activeListViewState.pageSize ?? this.pageSize,
activeListViewState.sortField,
activeListViewState.sortReverse,
activeListViewState.filterRules,
@ -362,6 +384,45 @@ export class DocumentListViewService {
this.saveDocumentListView()
}
set displayMode(mode: DisplayMode) {
this.activeListViewState.displayMode = mode
this.saveDocumentListView()
}
get displayMode(): DisplayMode {
const mode = this.activeListViewState.displayMode ?? DisplayMode.SMALL_CARDS
if (mode === ('details' as any)) {
// legacy
return DisplayMode.TABLE
}
return mode
}
set pageSize(size: number) {
this.activeListViewState.pageSize = size
this.reload()
this.saveDocumentListView()
}
get pageSize(): number {
return (
this.activeListViewState.pageSize ??
this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
)
}
get documentDisplayFields(): DocumentDisplayField[] {
return (
this.activeListViewState.documentDisplayFields ??
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
)
}
set documentDisplayFields(fields: DocumentDisplayField[]) {
this.activeListViewState.documentDisplayFields = fields
this.saveDocumentListView()
}
private saveDocumentListView() {
if (this._activeSavedViewId == null) {
let savedState: ListViewState = {
@ -370,6 +431,8 @@ export class DocumentListViewService {
filterRules: this.activeListViewState.filterRules,
sortField: this.activeListViewState.sortField,
sortReverse: this.activeListViewState.sortReverse,
displayMode: this.activeListViewState.displayMode,
documentDisplayFields: this.activeListViewState.documentDisplayFields,
}
localStorage.setItem(
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
@ -385,7 +448,7 @@ export class DocumentListViewService {
}
getLastPage(): number {
return Math.ceil(this.collectionSize / this.currentPageSize)
return Math.ceil(this.collectionSize / this.pageSize)
}
hasNext(doc: number) {
@ -452,13 +515,6 @@ export class DocumentListViewService {
})
}
updatePageSize() {
let newPageSize = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
if (newPageSize != this.currentPageSize) {
this.currentPageSize = newPageSize
}
}
selectNone() {
this.selected.clear()
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null