Customizable documents view
Fix
This commit is contained in:
parent
ffae52c9ac
commit
eb1eab1dfc
File diff suppressed because it is too large
Load Diff
@ -352,19 +352,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@if (savedViewGroup.get(view.id.toString()).get('show_on_dashboard').value) {
|
<div class="col">
|
||||||
<div class="col">
|
<pngx-input-number i18n-title title="Documents page size" [showAdd]="false" formControlName="page_size"></pngx-input-number>
|
||||||
<pngx-input-number i18n-title title="Widget list limit" [showAdd]="false" formControlName="page_size"></pngx-input-number>
|
</div>
|
||||||
</div>
|
<div class="col">
|
||||||
<div class="col">
|
<label class="form-label" for="display_mode_{{view.id}}" i18n>Display as</label>
|
||||||
<label class="form-label" for="display_mode_{{view.id}}" i18n>Widget display</label>
|
<select class="form-select" formControlName="display_mode">
|
||||||
<select class="form-select" formControlName="display_mode">
|
<option [ngValue]="DashboardViewMode.TABLE" i18n>Table</option>
|
||||||
<option [ngValue]="DashboardViewMode.TABLE" i18n>Table</option>
|
<option [ngValue]="DashboardViewMode.SMALL_CARDS" i18n>Small Cards</option>
|
||||||
<option [ngValue]="DashboardViewMode.SMALL_CARDS" i18n>Small Cards</option>
|
<option [ngValue]="DashboardViewMode.LARGE_CARDS" i18n>Large Cards</option>
|
||||||
<option [ngValue]="DashboardViewMode.LARGE_CARDS" i18n>Large Cards</option>
|
</select>
|
||||||
</select>
|
</div>
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (documentDisplayFields) {
|
@if (documentDisplayFields) {
|
||||||
<pngx-input-drag-drop-select i18n-title title="Show" [items]="documentDisplayFields" formControlName="document_display_fields"></pngx-input-drag-drop-select>
|
<pngx-input-drag-drop-select i18n-title title="Show" [items]="documentDisplayFields" formControlName="document_display_fields"></pngx-input-drag-drop-select>
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,6 @@ export class SettingsComponent
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
this.store.next(this.settingsForm.value)
|
this.store.next(this.settingsForm.value)
|
||||||
this.documentListViewService.updatePageSize()
|
|
||||||
this.settings.updateAppearanceSettings()
|
this.settings.updateAppearanceSettings()
|
||||||
let savedToast: Toast = {
|
let savedToast: Toast = {
|
||||||
content: $localize`Settings were saved successfully.`,
|
content: $localize`Settings were saved successfully.`,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@for (column of savedView.document_display_fields; track column; let i = $index) {
|
@for (column of savedView.document_display_fields; track column; let i = $index) {
|
||||||
@if (activeDisplayFields.has(column)) {
|
@if (activeDisplayFields.includes(column)) {
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
@ -31,7 +31,7 @@
|
|||||||
@for (doc of documents; track doc.id) {
|
@for (doc of documents; track doc.id) {
|
||||||
<tr>
|
<tr>
|
||||||
@for (column of savedView.document_display_fields; track column; let i = $index) {
|
@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 }">
|
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': i > 1 }">
|
||||||
@switch (column) {
|
@switch (column) {
|
||||||
@case (DashboardViewTableColumn.ADDED) {
|
@case (DashboardViewTableColumn.ADDED) {
|
||||||
|
@ -36,7 +36,6 @@ import {
|
|||||||
} from 'src/app/services/permissions.service'
|
} from 'src/app/services/permissions.service'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
import { CustomField, CustomFieldDataType } from 'src/app/data/custom-field'
|
||||||
import { Results } from 'src/app/data/results'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pngx-saved-view-widget',
|
selector: 'pngx-saved-view-widget',
|
||||||
@ -81,11 +80,11 @@ export class SavedViewWidgetComponent
|
|||||||
mouseOnPreview = false
|
mouseOnPreview = false
|
||||||
popoverHidden = true
|
popoverHidden = true
|
||||||
|
|
||||||
activeDisplayFields: Set<DocumentDisplayField> = new Set([
|
activeDisplayFields: DocumentDisplayField[] = [
|
||||||
DocumentDisplayField.TITLE,
|
DocumentDisplayField.TITLE,
|
||||||
DocumentDisplayField.CREATED,
|
DocumentDisplayField.CREATED,
|
||||||
DocumentDisplayField.ADDED,
|
DocumentDisplayField.ADDED,
|
||||||
])
|
]
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.reload()
|
this.reload()
|
||||||
@ -121,7 +120,7 @@ export class SavedViewWidgetComponent
|
|||||||
type &&
|
type &&
|
||||||
this.permissionsService.currentUserCan(PermissionAction.View, type)
|
this.permissionsService.currentUserCan(PermissionAction.View, type)
|
||||||
)
|
)
|
||||||
this.activeDisplayFields.add(column)
|
this.activeDisplayFields.push(column)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title">
|
<h5 class="card-title">
|
||||||
@if (displayFields.has(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
@if (displayFields.includes(DocumentDisplayField.CORRESPONDENT) && document.correspondent) {
|
||||||
@if (clickCorrespondent.observers.length ) {
|
@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>
|
<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 {
|
} @else {
|
||||||
@ -23,10 +23,10 @@
|
|||||||
}
|
}
|
||||||
:
|
:
|
||||||
}
|
}
|
||||||
@if (displayFields.has(DocumentDisplayField.TITLE)) {
|
@if (displayFields.includes(DocumentDisplayField.TITLE)) {
|
||||||
{{document.title | documentTitle}}
|
{{document.title | documentTitle}}
|
||||||
}
|
}
|
||||||
@if (displayFields.has(DocumentDisplayField.TAGS)) {
|
@if (displayFields.includes(DocumentDisplayField.TAGS)) {
|
||||||
@for (t of document.tags$ | async; track t) {
|
@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>
|
<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>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<p class="card-text">
|
<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>
|
<span [innerHtml]="document.__search_hit__.highlights"></span>
|
||||||
}
|
}
|
||||||
@for (highlight of searchNoteHighlights; track highlight) {
|
@for (highlight of searchNoteHighlights; track highlight) {
|
||||||
@ -43,7 +43,7 @@
|
|||||||
<span [innerHtml]="highlight"></span>
|
<span [innerHtml]="highlight"></span>
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@if (!document.__search_hit__) {
|
@if (!document.__search_hit__?.score) {
|
||||||
<span class="result-content">{{contentTrimmed}}</span>
|
<span class="result-content">{{contentTrimmed}}</span>
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
@ -70,29 +70,29 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="list-group list-group-horizontal border-0 card-info ms-md-auto mt-2 mt-md-0">
|
<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>
|
<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>
|
<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>
|
</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
|
<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()">
|
(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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="file-earmark"></i-bs><small>{{(document.document_type$ | async)?.name}}</small>
|
||||||
</button>
|
</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
|
<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()">
|
(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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="archive"></i-bs><small>{{(document.storage_path$ | async)?.name}}</small>
|
||||||
</button>
|
</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">
|
<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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="upc-scan"></i-bs><small>#{{document.archive_serial_number}}</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (displayFields.has(DocumentDisplayField.CREATED) || displayFields.has(DocumentDisplayField.ADDED)) {
|
@if (displayFields.includes(DocumentDisplayField.CREATED) || displayFields.includes(DocumentDisplayField.ADDED)) {
|
||||||
<ng-template #dateTooltip>
|
<ng-template #dateTooltip>
|
||||||
<div class="d-flex flex-column text-light">
|
<div class="d-flex flex-column text-light">
|
||||||
<span i18n>Created: {{ document.created | customDate }}</span>
|
<span i18n>Created: {{ document.created | customDate }}</span>
|
||||||
@ -100,19 +100,23 @@
|
|||||||
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
<span i18n>Modified: {{ document.modified | customDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
@if (displayFields.includes(DocumentDisplayField.CREATED)) {
|
||||||
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>
|
<div class="list-group-item bg-light text-dark p-1 border-0 d-flex align-items-center" [ngbTooltip]="dateTooltip">
|
||||||
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created_date | customDate:'mediumDate'}} }
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="calendar-event"></i-bs><small>{{document.created_date | customDate:'mediumDate'}}</small>
|
||||||
@else { {{document.added | customDate:'mediumDate'}} }
|
</div>
|
||||||
</small>
|
}
|
||||||
</div>
|
@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.has(DocumentDisplayField.OWNER) && document.owner && document.owner !== settingsService.currentUser.id) {
|
@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">
|
<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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="person-fill-lock"></i-bs><small>{{document.owner | username}}</small>
|
||||||
</div>
|
</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">
|
<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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="people-fill"></i-bs><small i18n>Shared</small>
|
||||||
</div>
|
</div>
|
||||||
@ -124,7 +128,7 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@for (field of document.custom_fields; track field.id) {
|
@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">
|
<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>
|
<i-bs width=".9em" height=".9em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
||||||
<small>
|
<small>
|
||||||
|
@ -35,9 +35,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
|
|||||||
selected = false
|
selected = false
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: Set<DocumentDisplayField | string> = new Set(
|
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
toggleSelected = new EventEmitter()
|
toggleSelected = new EventEmitter()
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="tags d-flex flex-column text-end position-absolute me-1 fs-6">
|
||||||
@for (t of getTagsLimited$() | async; track t) {
|
@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>
|
<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>
|
</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">
|
<a routerLink="/documents/{{document.id}}/notes" class="document-card-notes py-2 px-1">
|
||||||
<span class="badge rounded-pill bg-light border text-primary">
|
<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>
|
<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">
|
<div class="card-body bg-light p-2">
|
||||||
<p class="card-text">
|
<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>:
|
<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}}
|
{{document.title | documentTitle}}
|
||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer pt-0 pb-2 px-2">
|
<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">
|
<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
|
<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()">
|
(click)="clickDocumentType.emit(document.document_type);$event.stopPropagation()">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="file-earmark"></i-bs>
|
||||||
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
<small>{{(document.document_type$ | async)?.name ?? privateName}}</small>
|
||||||
</button>
|
</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
|
<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()">
|
(click)="clickStoragePath.emit(document.storage_path);$event.stopPropagation()">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="folder"></i-bs>
|
||||||
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
<small>{{(document.storage_path$ | async)?.name ?? privateName}}</small>
|
||||||
</button>
|
</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">
|
<div class="list-group-item bg-transparent p-0 border-0 d-flex flex-wrap-reverse justify-content-between">
|
||||||
<ng-template #dateTooltip>
|
<ng-template #dateTooltip>
|
||||||
<div class="d-flex flex-column text-light">
|
<div class="d-flex flex-column text-light">
|
||||||
@ -70,32 +70,32 @@
|
|||||||
<div class="ps-0 p-1" placement="top" [ngbTooltip]="dateTooltip">
|
<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>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="calendar-event"></i-bs>
|
||||||
<small>
|
<small>
|
||||||
@if (displayFields.has(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
|
@if (displayFields.includes(DocumentDisplayField.CREATED)) { {{document.created | customDate:'mediumDate'}} }
|
||||||
@else { {{document.added | customDate:'mediumDate'}} }
|
@else { {{document.added | customDate:'mediumDate'}} }
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="upc-scan"></i-bs>
|
||||||
<small>#{{document.archive_serial_number}}</small>
|
<small>#{{document.archive_serial_number}}</small>
|
||||||
</div>
|
</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">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="person-fill-lock"></i-bs>
|
||||||
<small>{{document.owner | username}}</small>
|
<small>{{document.owner | username}}</small>
|
||||||
</div>
|
</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">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
|
<i-bs width="1em" height="1em" class="me-2 text-muted" name="people-fill"></i-bs>
|
||||||
<small i18n>Shared</small>
|
<small i18n>Shared</small>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@for (field of document.custom_fields; track field.id) {
|
@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">
|
<div class="ps-0 p-1">
|
||||||
<i-bs width="1em" height="1em" class="me-2 text-muted" name="ui-radios"></i-bs>
|
<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>
|
<pngx-custom-field-display [document]="document" [fieldId]="field.field"></pngx-custom-field-display>
|
||||||
|
@ -42,9 +42,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
|
|||||||
document: Document
|
document: Document
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
displayFields: Set<DocumentDisplayField | string> = new Set(
|
displayFields: string[] = DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
||||||
DEFAULT_DOCUMENT_DISPLAY_FIELDS.map((f) => f.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
@Output()
|
@Output()
|
||||||
dblClickDocument = new EventEmitter()
|
dblClickDocument = new EventEmitter()
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<div class="px-3">
|
<div class="px-3">
|
||||||
@for (field of settingsService.allDocumentDisplayFields; track field.id) {
|
@for (field of settingsService.allDocumentDisplayFields; track field.id) {
|
||||||
<div class="form-check small">
|
<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>
|
<label class="form-check-label small" for="displayField{{field.id}}">{{field.name}}</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -28,15 +28,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group flex-fill" role="group">
|
<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">
|
<label for="displayModeDetails" class="btn btn-outline-primary btn-sm">
|
||||||
<i-bs name="list-ul"></i-bs>
|
<i-bs name="list-ul"></i-bs>
|
||||||
</label>
|
</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">
|
<label for="displayModeSmall" class="btn btn-outline-primary btn-sm">
|
||||||
<i-bs name="grid"></i-bs>
|
<i-bs name="grid"></i-bs>
|
||||||
</label>
|
</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">
|
<label for="displayModeLarge" class="btn btn-outline-primary btn-sm">
|
||||||
<i-bs name="hdd-stack"></i-bs>
|
<i-bs name="hdd-stack"></i-bs>
|
||||||
</label>
|
</label>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@for (f of getSortFields(); track f) {
|
@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}}
|
[class.active]="list.sortField === f.field">{{f.name}}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@if (list.collectionSize) {
|
@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>
|
[rotate]="true" aria-label="Default pagination" size="sm"></ngb-pagination>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@ -138,7 +138,7 @@
|
|||||||
@if (list.error ) {
|
@if (list.error ) {
|
||||||
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
<div class="alert alert-danger" role="alert"><ng-container i18n>Error while loading documents</ng-container>: {{list.error}}</div>
|
||||||
} @else {
|
} @else {
|
||||||
@if (displayMode === 'largeCards') {
|
@if (list.displayMode === DisplayMode.LARGE_CARDS) {
|
||||||
<div>
|
<div>
|
||||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||||
<pngx-document-card-large
|
<pngx-document-card-large
|
||||||
@ -156,11 +156,11 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (displayMode === 'details' || displayMode === 'table') {
|
@if (list.displayMode === DisplayMode.TABLE) {
|
||||||
<table class="table table-sm align-middle border shadow-sm">
|
<table class="table table-sm align-middle border shadow-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<th></th>
|
<th></th>
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
|
||||||
<th class="d-none d-lg-table-cell"
|
<th class="d-none d-lg-table-cell"
|
||||||
pngxSortable="archive_serial_number"
|
pngxSortable="archive_serial_number"
|
||||||
title="Sort by ASN" i18n-title
|
title="Sort by ASN" i18n-title
|
||||||
@ -169,7 +169,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>ASN</th>
|
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"
|
<th class="d-none d-md-table-cell"
|
||||||
pngxSortable="correspondent__name"
|
pngxSortable="correspondent__name"
|
||||||
title="Sort by correspondent" i18n-title
|
title="Sort by correspondent" i18n-title
|
||||||
@ -178,7 +178,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Correspondent</th>
|
i18n>Correspondent</th>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.TITLE)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.TITLE)) {
|
||||||
<th
|
<th
|
||||||
pngxSortable="title"
|
pngxSortable="title"
|
||||||
title="Sort by title" i18n-title
|
title="Sort by title" i18n-title
|
||||||
@ -187,7 +187,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Title</th>
|
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"
|
<th class="d-none d-xl-table-cell"
|
||||||
pngxSortable="owner"
|
pngxSortable="owner"
|
||||||
title="Sort by owner" i18n-title
|
title="Sort by owner" i18n-title
|
||||||
@ -196,7 +196,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Owner</th>
|
i18n>Owner</th>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||||
<th class="d-none d-xl-table-cell"
|
<th class="d-none d-xl-table-cell"
|
||||||
pngxSortable="num_notes"
|
pngxSortable="num_notes"
|
||||||
title="Sort by notes" i18n-title
|
title="Sort by notes" i18n-title
|
||||||
@ -205,7 +205,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Notes</th>
|
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"
|
<th class="d-none d-xl-table-cell"
|
||||||
pngxSortable="document_type__name"
|
pngxSortable="document_type__name"
|
||||||
title="Sort by document type" i18n-title
|
title="Sort by document type" i18n-title
|
||||||
@ -214,7 +214,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Document type</th>
|
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"
|
<th class="d-none d-xl-table-cell"
|
||||||
pngxSortable="storage_path__name"
|
pngxSortable="storage_path__name"
|
||||||
title="Sort by storage path" i18n-title
|
title="Sort by storage path" i18n-title
|
||||||
@ -223,7 +223,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Storage path</th>
|
i18n>Storage path</th>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
|
||||||
<th
|
<th
|
||||||
pngxSortable="created"
|
pngxSortable="created"
|
||||||
title="Sort by created date" i18n-title
|
title="Sort by created date" i18n-title
|
||||||
@ -232,7 +232,7 @@
|
|||||||
(sort)="onSort($event)"
|
(sort)="onSort($event)"
|
||||||
i18n>Created</th>
|
i18n>Created</th>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
|
||||||
<th
|
<th
|
||||||
pngxSortable="added"
|
pngxSortable="added"
|
||||||
title="Sort by added date" i18n-title
|
title="Sort by added date" i18n-title
|
||||||
@ -256,12 +256,12 @@
|
|||||||
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
<label class="form-check-label" for="docCheck{{d.id}}"></label>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.ASN)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.ASN)) {
|
||||||
<td class="d-none d-xl-table-cell">
|
<td class="d-none d-xl-table-cell">
|
||||||
{{d.archive_serial_number}}
|
{{d.archive_serial_number}}
|
||||||
</td>
|
</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">
|
<td class="d-none d-xl-table-cell">
|
||||||
@if (d.correspondent) {
|
@if (d.correspondent) {
|
||||||
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
<a (click)="clickCorrespondent(d.correspondent);$event.stopPropagation()" title="Filter by correspondent" i18n-title>{{(d.correspondent$ | async)?.name}}</a>
|
||||||
@ -269,21 +269,21 @@
|
|||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
<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>
|
<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) {
|
@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>
|
<pngx-tag [tag]="t" class="ms-1" clickable="true" linkTitle="Filter by tag" i18n-linkTitle (click)="clickTag(t.id);$event.stopPropagation()"></pngx-tag>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.OWNER)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.OWNER) && permissionService.currentUserCan(PermissionAction.View, PermissionType.User)) {
|
||||||
<td>
|
<td>
|
||||||
{{d.owner | username}}
|
{{d.owner | username}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.NOTES) && notesEnabled) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.NOTES) && notesEnabled) {
|
||||||
<td class="d-none d-xl-table-cell">
|
<td class="d-none d-xl-table-cell">
|
||||||
@if (d.notes.length) {
|
@if (d.notes.length) {
|
||||||
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
<a routerLink="/documents/{{d.id}}/notes" class="btn btn-sm p-0">
|
||||||
@ -294,26 +294,26 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</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">
|
<td class="d-none d-xl-table-cell">
|
||||||
@if (d.document_type) {
|
@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>
|
<a (click)="clickDocumentType(d.document_type);$event.stopPropagation()" title="Filter by document type" i18n-title>{{(d.document_type$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
</td>
|
</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">
|
<td class="d-none d-xl-table-cell">
|
||||||
@if (d.storage_path) {
|
@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>
|
<a (click)="clickStoragePath(d.storage_path);$event.stopPropagation()" title="Filter by storage path" i18n-title>{{(d.storage_path$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.CREATED)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.CREATED)) {
|
||||||
<td>
|
<td>
|
||||||
{{d.created_date | customDate}}
|
{{d.created_date | customDate}}
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
@if (activeDisplayFields.has(DocumentDisplayField.ADDED)) {
|
@if (activeDisplayFields.includes(DocumentDisplayField.ADDED)) {
|
||||||
<td>
|
<td>
|
||||||
{{d.added | customDate}}
|
{{d.added | customDate}}
|
||||||
</td>
|
</td>
|
||||||
@ -328,7 +328,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
}
|
}
|
||||||
@if (displayMode === 'smallCards') {
|
@if (list.displayMode === DisplayMode.SMALL_CARDS) {
|
||||||
<div class="row row-cols-paperless-cards">
|
<div class="row row-cols-paperless-cards">
|
||||||
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
@for (d of list.documents; track trackByDocumentId($index, d)) {
|
||||||
<pngx-document-card-small class="p-0"
|
<pngx-document-card-small class="p-0"
|
||||||
|
@ -80,3 +80,7 @@ $paperless-card-breakpoints: (
|
|||||||
a {
|
a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pngx-page-header .dropdown-menu {
|
||||||
|
--bs-dropdown-min-width: 12em;
|
||||||
|
}
|
||||||
|
@ -34,7 +34,12 @@ import {
|
|||||||
import { Subject, of, throwError } from 'rxjs'
|
import { Subject, of, throwError } from 'rxjs'
|
||||||
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
|
||||||
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
|
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 {
|
import {
|
||||||
FILTER_FULLTEXT_MORELIKE,
|
FILTER_FULLTEXT_MORELIKE,
|
||||||
FILTER_FULLTEXT_QUERY,
|
FILTER_FULLTEXT_QUERY,
|
||||||
@ -169,17 +174,6 @@ describe('DocumentListComponent', () => {
|
|||||||
component = fixture.componentInstance
|
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', () => {
|
it('should reload on new document consumed', () => {
|
||||||
const reloadSpy = jest.spyOn(documentListService, 'reload')
|
const reloadSpy = jest.spyOn(documentListService, 'reload')
|
||||||
const fileStatusSubject = new Subject<FileStatus>()
|
const fileStatusSubject = new Subject<FileStatus>()
|
||||||
@ -297,18 +291,18 @@ describe('DocumentListComponent', () => {
|
|||||||
const displayModeButtons = fixture.debugElement.queryAll(
|
const displayModeButtons = fixture.debugElement.queryAll(
|
||||||
By.css('input[type="radio"]')
|
By.css('input[type="radio"]')
|
||||||
)
|
)
|
||||||
expect(component.displayMode).toEqual('smallCards')
|
expect(component.list.displayMode).toEqual('smallCards')
|
||||||
|
|
||||||
displayModeButtons[0].nativeElement.checked = true
|
displayModeButtons[0].nativeElement.checked = true
|
||||||
displayModeButtons[0].triggerEventHandler('change')
|
displayModeButtons[0].triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('table')
|
expect(component.list.displayMode).toEqual('table')
|
||||||
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
|
expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(3)
|
||||||
|
|
||||||
displayModeButtons[1].nativeElement.checked = true
|
displayModeButtons[1].nativeElement.checked = true
|
||||||
displayModeButtons[1].triggerEventHandler('change')
|
displayModeButtons[1].triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('smallCards')
|
expect(component.list.displayMode).toEqual('smallCards')
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
|
fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
|
||||||
).toHaveLength(3)
|
).toHaveLength(3)
|
||||||
@ -316,7 +310,7 @@ describe('DocumentListComponent', () => {
|
|||||||
displayModeButtons[2].nativeElement.checked = true
|
displayModeButtons[2].nativeElement.checked = true
|
||||||
displayModeButtons[2].triggerEventHandler('change')
|
displayModeButtons[2].triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('largeCards')
|
expect(component.list.displayMode).toEqual('largeCards')
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
|
fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
|
||||||
).toHaveLength(3)
|
).toHaveLength(3)
|
||||||
@ -337,7 +331,7 @@ describe('DocumentListComponent', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should support setting sort field by table head', () => {
|
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)
|
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(documentListService.sortField).toEqual('created')
|
expect(documentListService.sortField).toEqual('created')
|
||||||
@ -348,7 +342,7 @@ describe('DocumentListComponent', () => {
|
|||||||
detailsDisplayModeButton.nativeElement.checked = true
|
detailsDisplayModeButton.nativeElement.checked = true
|
||||||
detailsDisplayModeButton.triggerEventHandler('change')
|
detailsDisplayModeButton.triggerEventHandler('change')
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
expect(component.displayMode).toEqual('table')
|
expect(component.list.displayMode).toEqual(DisplayMode.TABLE)
|
||||||
|
|
||||||
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
|
const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
|
||||||
sortTh.triggerEventHandler('click')
|
sortTh.triggerEventHandler('click')
|
||||||
@ -547,6 +541,42 @@ describe('DocumentListComponent', () => {
|
|||||||
expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
|
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', () => {
|
it('should navigate to a document', () => {
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
const routerSpy = jest.spyOn(router, 'navigate')
|
const routerSpy = jest.spyOn(router, 'navigate')
|
||||||
@ -559,12 +589,15 @@ describe('DocumentListComponent', () => {
|
|||||||
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
|
||||||
expect(documentListService.sortField).toEqual('created')
|
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()
|
fixture.detectChanges()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
).toHaveLength(8)
|
).toHaveLength(9)
|
||||||
|
|
||||||
expect(component.notesEnabled).toBeTruthy()
|
expect(component.notesEnabled).toBeTruthy()
|
||||||
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
|
settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
|
||||||
@ -572,7 +605,7 @@ describe('DocumentListComponent', () => {
|
|||||||
expect(component.notesEnabled).toBeFalsy()
|
expect(component.notesEnabled).toBeFalsy()
|
||||||
expect(
|
expect(
|
||||||
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
fixture.debugElement.queryAll(By.directive(SortableDirective))
|
||||||
).toHaveLength(7)
|
).toHaveLength(8)
|
||||||
|
|
||||||
// insufficient perms
|
// insufficient perms
|
||||||
jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
|
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', () => {
|
it('should support toggling display fields', () => {
|
||||||
fixture.detectChanges()
|
fixture.detectChanges()
|
||||||
component.activeDisplayFields = new Set([DocumentDisplayField.ASN])
|
component.activeDisplayFields = [DocumentDisplayField.ASN]
|
||||||
component.toggleDisplayField(DocumentDisplayField.TITLE)
|
component.toggleDisplayField(DocumentDisplayField.TITLE)
|
||||||
expect(component.activeDisplayFields).toEqual(
|
expect(component.activeDisplayFields).toEqual([
|
||||||
new Set([DocumentDisplayField.ASN, DocumentDisplayField.TITLE])
|
DocumentDisplayField.ASN,
|
||||||
)
|
DocumentDisplayField.TITLE,
|
||||||
|
])
|
||||||
component.toggleDisplayField(DocumentDisplayField.ASN)
|
component.toggleDisplayField(DocumentDisplayField.ASN)
|
||||||
expect(component.activeDisplayFields).toEqual(
|
expect(component.activeDisplayFields).toEqual([DocumentDisplayField.TITLE])
|
||||||
new Set([DocumentDisplayField.TITLE])
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should get custom field title', () => {
|
it('should get custom field title', () => {
|
||||||
|
@ -51,6 +51,7 @@ export class DocumentListComponent
|
|||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
DocumentDisplayField = DocumentDisplayField
|
DocumentDisplayField = DocumentDisplayField
|
||||||
|
DisplayMode = DisplayMode
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public list: DocumentListViewService,
|
public list: DocumentListViewService,
|
||||||
@ -72,27 +73,12 @@ export class DocumentListComponent
|
|||||||
|
|
||||||
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
|
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>
|
||||||
|
|
||||||
displayMode: string = DisplayMode.SMALL_CARDS // largeCards, smallCards, table
|
get activeDisplayFields(): DocumentDisplayField[] {
|
||||||
|
return this.list.documentDisplayFields
|
||||||
_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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set activeDisplayFields(fields: Set<DocumentDisplayField | string>) {
|
set activeDisplayFields(fields: DocumentDisplayField[]) {
|
||||||
this._activeDisplayFields = fields
|
this.list.documentDisplayFields = fields
|
||||||
this.updateDisplayCustomFields()
|
this.updateDisplayCustomFields()
|
||||||
}
|
}
|
||||||
activeDisplayCustomFields: Set<string> = new Set()
|
activeDisplayCustomFields: Set<string> = new Set()
|
||||||
@ -118,6 +104,11 @@ export class DocumentListComponent
|
|||||||
return (
|
return (
|
||||||
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
|
this.unmodifiedSavedView.sort_field !== this.list.sortField ||
|
||||||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
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(
|
filterRulesDiffer(
|
||||||
this.unmodifiedSavedView.filter_rules,
|
this.unmodifiedSavedView.filter_rules,
|
||||||
this.list.filterRules
|
this.list.filterRules
|
||||||
@ -154,10 +145,6 @@ export class DocumentListComponent
|
|||||||
return this.list.sortReverse
|
return this.list.sortReverse
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortField(field: string) {
|
|
||||||
this.list.sortField = field
|
|
||||||
}
|
|
||||||
|
|
||||||
onSort(event: SortEvent) {
|
onSort(event: SortEvent) {
|
||||||
this.list.setSort(event.column, event.reverse)
|
this.list.setSort(event.column, event.reverse)
|
||||||
}
|
}
|
||||||
@ -166,25 +153,15 @@ export class DocumentListComponent
|
|||||||
return this.list.selected.size > 0
|
return this.list.selected.size > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
saveDisplayMode() {
|
toggleDisplayField(field: DocumentDisplayField) {
|
||||||
localStorage.setItem('document-list:displayMode', this.displayMode)
|
if (this.activeDisplayFields.includes(field)) {
|
||||||
}
|
this.activeDisplayFields = this.activeDisplayFields.filter(
|
||||||
|
(f) => f !== field
|
||||||
saveDisplayFields() {
|
)
|
||||||
localStorage.setItem(
|
|
||||||
'document-list:displayFields',
|
|
||||||
JSON.stringify(Array.from(this.activeDisplayFields))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDisplayField(field: string) {
|
|
||||||
if (this.activeDisplayFields.has(field)) {
|
|
||||||
this.activeDisplayFields.delete(field)
|
|
||||||
} else {
|
} else {
|
||||||
this.activeDisplayFields.add(field)
|
this.activeDisplayFields = [...this.activeDisplayFields, field]
|
||||||
}
|
}
|
||||||
this.updateDisplayCustomFields()
|
this.updateDisplayCustomFields()
|
||||||
this.saveDisplayFields()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDisplayCustomFieldTitle(field: string) {
|
public getDisplayCustomFieldTitle(field: string) {
|
||||||
@ -194,16 +171,6 @@ export class DocumentListComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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
|
this.consumerStatusService
|
||||||
.onDocumentConsumptionFinished()
|
.onDocumentConsumptionFinished()
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
@ -228,6 +195,12 @@ export class DocumentListComponent
|
|||||||
})
|
})
|
||||||
return
|
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.unmodifiedSavedView = view
|
||||||
this.list.activateSavedViewWithQueryParams(
|
this.list.activateSavedViewWithQueryParams(
|
||||||
view,
|
view,
|
||||||
|
@ -12,13 +12,19 @@ import {
|
|||||||
FILTER_HAS_TAGS_ALL,
|
FILTER_HAS_TAGS_ALL,
|
||||||
FILTER_HAS_TAGS_ANY,
|
FILTER_HAS_TAGS_ANY,
|
||||||
} from '../data/filter-rule-type'
|
} 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 { FilterRule } from '../data/filter-rule'
|
||||||
import { RouterTestingModule } from '@angular/router/testing'
|
import { RouterTestingModule } from '@angular/router/testing'
|
||||||
import { routes } from 'src/app/app-routing.module'
|
import { routes } from 'src/app/app-routing.module'
|
||||||
import { PermissionsGuard } from '../guards/permissions.guard'
|
import { PermissionsGuard } from '../guards/permissions.guard'
|
||||||
import { SettingsService } from './settings.service'
|
import { SettingsService } from './settings.service'
|
||||||
import { SETTINGS_KEYS } from '../data/ui-settings'
|
import { SETTINGS_KEYS } from '../data/ui-settings'
|
||||||
|
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||||
|
|
||||||
const documents = [
|
const documents = [
|
||||||
{
|
{
|
||||||
@ -213,7 +219,7 @@ describe('DocumentListViewService', () => {
|
|||||||
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${
|
`${environment.apiBaseUrl}documents/?page=${page}&page_size=${
|
||||||
documentListViewService.currentPageSize
|
documentListViewService.pageSize
|
||||||
}&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
|
}&ordering=${reverse ? '-' : ''}${sort}&truncate_content=true`
|
||||||
)
|
)
|
||||||
expect(req.request.method).toEqual('GET')
|
expect(req.request.method).toEqual('GET')
|
||||||
@ -231,7 +237,7 @@ describe('DocumentListViewService', () => {
|
|||||||
}
|
}
|
||||||
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
documentListViewService.loadFromQueryParams(convertToParamMap(params))
|
||||||
let req = httpTestingController.expectOne(
|
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(req.request.method).toEqual('GET')
|
||||||
expect(documentListViewService.filterRules).toEqual([
|
expect(documentListViewService.filterRules).toEqual([
|
||||||
@ -249,7 +255,7 @@ describe('DocumentListViewService', () => {
|
|||||||
it('should use filter rules to update query params', () => {
|
it('should use filter rules to update query params', () => {
|
||||||
documentListViewService.filterRules = filterRules
|
documentListViewService.filterRules = filterRules
|
||||||
const req = httpTestingController.expectOne(
|
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')
|
expect(req.request.method).toEqual('GET')
|
||||||
})
|
})
|
||||||
@ -257,7 +263,7 @@ describe('DocumentListViewService', () => {
|
|||||||
it('should support quick filter', () => {
|
it('should support quick filter', () => {
|
||||||
documentListViewService.quickFilter(filterRules)
|
documentListViewService.quickFilter(filterRules)
|
||||||
const req = httpTestingController.expectOne(
|
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')
|
expect(req.request.method).toEqual('GET')
|
||||||
})
|
})
|
||||||
@ -280,7 +286,7 @@ describe('DocumentListViewService', () => {
|
|||||||
convertToParamMap(params)
|
convertToParamMap(params)
|
||||||
)
|
)
|
||||||
let req = httpTestingController.expectOne(
|
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')
|
expect(req.request.method).toEqual('GET')
|
||||||
// reset the list
|
// reset the list
|
||||||
@ -305,8 +311,7 @@ describe('DocumentListViewService', () => {
|
|||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
|
||||||
)
|
)
|
||||||
expect(documentListViewService.currentPage).toEqual(1)
|
expect(documentListViewService.currentPage).toEqual(1)
|
||||||
documentListViewService.currentPageSize = 3
|
documentListViewService.pageSize = 3
|
||||||
documentListViewService.reload()
|
|
||||||
req = httpTestingController.expectOne(
|
req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
||||||
)
|
)
|
||||||
@ -362,7 +367,10 @@ describe('DocumentListViewService', () => {
|
|||||||
.spyOn(documentListViewService, 'documents', 'get')
|
.spyOn(documentListViewService, 'documents', 'get')
|
||||||
.mockReturnValue(documents)
|
.mockReturnValue(documents)
|
||||||
expect(documentListViewService.currentPage).toEqual(1)
|
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
|
jest
|
||||||
.spyOn(documentListViewService, 'getLastPage')
|
.spyOn(documentListViewService, 'getLastPage')
|
||||||
.mockReturnValue(Math.ceil(documents.length / 3))
|
.mockReturnValue(Math.ceil(documents.length / 3))
|
||||||
@ -410,7 +418,13 @@ describe('DocumentListViewService', () => {
|
|||||||
.spyOn(documentListViewService, 'documents', 'get')
|
.spyOn(documentListViewService, 'documents', 'get')
|
||||||
.mockReturnValue(documents)
|
.mockReturnValue(documents)
|
||||||
documentListViewService.currentPage = 2
|
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')
|
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
|
||||||
documentListViewService.getPrevious(1).subscribe({
|
documentListViewService.getPrevious(1).subscribe({
|
||||||
next: () => {},
|
next: () => {},
|
||||||
@ -426,8 +440,7 @@ describe('DocumentListViewService', () => {
|
|||||||
|
|
||||||
it('should update page size from settings', () => {
|
it('should update page size from settings', () => {
|
||||||
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
|
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
|
||||||
documentListViewService.updatePageSize()
|
expect(documentListViewService.pageSize).toEqual(10)
|
||||||
expect(documentListViewService.currentPageSize).toEqual(10)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should support select a document', () => {
|
it('should support select a document', () => {
|
||||||
@ -459,8 +472,7 @@ describe('DocumentListViewService', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should support select page', () => {
|
it('should support select page', () => {
|
||||||
documentListViewService.currentPageSize = 3
|
documentListViewService.pageSize = 3
|
||||||
documentListViewService.reload()
|
|
||||||
const req = httpTestingController.expectOne(
|
const req = httpTestingController.expectOne(
|
||||||
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
|
`${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`
|
`${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)
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -8,7 +8,12 @@ import {
|
|||||||
isFullTextFilterRule,
|
isFullTextFilterRule,
|
||||||
} from '../utils/filter-rules'
|
} from '../utils/filter-rules'
|
||||||
import { Document } from '../data/document'
|
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 { SETTINGS_KEYS } from '../data/ui-settings'
|
||||||
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
import { DOCUMENT_LIST_SERVICE } from '../data/storage-keys'
|
||||||
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
|
import { paramsFromViewState, paramsToViewState } from '../utils/query-params'
|
||||||
@ -59,6 +64,21 @@ export interface ListViewState {
|
|||||||
* Contains the IDs of all selected documents.
|
* Contains the IDs of all selected documents.
|
||||||
*/
|
*/
|
||||||
selected?: Set<number>
|
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
|
selectionData?: SelectionData
|
||||||
|
|
||||||
currentPageSize: number = this.settings.get(SETTINGS_KEYS.DOCUMENT_LIST_SIZE)
|
|
||||||
|
|
||||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
|
|
||||||
private listViewStates: Map<number, ListViewState> = new Map()
|
private listViewStates: Map<number, ListViewState> = new Map()
|
||||||
@ -113,7 +131,7 @@ export class DocumentListViewService {
|
|||||||
delete savedState[k]
|
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)
|
let newState = Object.assign(this.defaultListViewState(), savedState)
|
||||||
this.listViewStates.set(null, newState)
|
this.listViewStates.set(null, newState)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -176,6 +194,10 @@ export class DocumentListViewService {
|
|||||||
if (this._activeSavedViewId) {
|
if (this._activeSavedViewId) {
|
||||||
this.activeListViewState.title = view.name
|
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()
|
this.reduceSelectionToFilter()
|
||||||
|
|
||||||
@ -220,7 +242,7 @@ export class DocumentListViewService {
|
|||||||
this.documentService
|
this.documentService
|
||||||
.listFiltered(
|
.listFiltered(
|
||||||
activeListViewState.currentPage,
|
activeListViewState.currentPage,
|
||||||
this.currentPageSize,
|
activeListViewState.pageSize ?? this.pageSize,
|
||||||
activeListViewState.sortField,
|
activeListViewState.sortField,
|
||||||
activeListViewState.sortReverse,
|
activeListViewState.sortReverse,
|
||||||
activeListViewState.filterRules,
|
activeListViewState.filterRules,
|
||||||
@ -362,6 +384,45 @@ export class DocumentListViewService {
|
|||||||
this.saveDocumentListView()
|
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() {
|
private saveDocumentListView() {
|
||||||
if (this._activeSavedViewId == null) {
|
if (this._activeSavedViewId == null) {
|
||||||
let savedState: ListViewState = {
|
let savedState: ListViewState = {
|
||||||
@ -370,6 +431,8 @@ export class DocumentListViewService {
|
|||||||
filterRules: this.activeListViewState.filterRules,
|
filterRules: this.activeListViewState.filterRules,
|
||||||
sortField: this.activeListViewState.sortField,
|
sortField: this.activeListViewState.sortField,
|
||||||
sortReverse: this.activeListViewState.sortReverse,
|
sortReverse: this.activeListViewState.sortReverse,
|
||||||
|
displayMode: this.activeListViewState.displayMode,
|
||||||
|
documentDisplayFields: this.activeListViewState.documentDisplayFields,
|
||||||
}
|
}
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
|
DOCUMENT_LIST_SERVICE.CURRENT_VIEW_CONFIG,
|
||||||
@ -385,7 +448,7 @@ export class DocumentListViewService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLastPage(): number {
|
getLastPage(): number {
|
||||||
return Math.ceil(this.collectionSize / this.currentPageSize)
|
return Math.ceil(this.collectionSize / this.pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasNext(doc: number) {
|
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() {
|
selectNone() {
|
||||||
this.selected.clear()
|
this.selected.clear()
|
||||||
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
this.rangeSelectionAnchorIndex = this.lastRangeSelectionToIndex = null
|
||||||
|
Loading…
x
Reference in New Issue
Block a user