Blank out display fields for better backwards compatibility
This commit is contained in:
parent
9a2d30cb33
commit
0e1ab79773
@ -1,6 +1,6 @@
|
|||||||
<div class="d-flex flex-row mt-2 align-items-center">
|
<div class="d-flex flex-row mt-2 align-items-center">
|
||||||
{{title}}:
|
<span class="me-2">{{title}}:</span>
|
||||||
<div class="ms-2 d-flex flex-row gap-2 w-100"
|
<div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
|
||||||
cdkDropList #selectedList="cdkDropList"
|
cdkDropList #selectedList="cdkDropList"
|
||||||
cdkDropListOrientation="horizontal"
|
cdkDropListOrientation="horizontal"
|
||||||
(cdkDropListDropped)="drop($event)"
|
(cdkDropListDropped)="drop($event)"
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-row mt-2 align-items-center bg-light p-2">
|
<div class="d-flex flex-row mt-2 align-items-center bg-light p-2">
|
||||||
<div class="d-flex flex-row gap-2 w-100"
|
<div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
|
||||||
cdkDropList #unselectedList="cdkDropList"
|
cdkDropList #unselectedList="cdkDropList"
|
||||||
cdkDropListOrientation="horizontal"
|
cdkDropListOrientation="horizontal"
|
||||||
(cdkDropListDropped)="drop($event)"
|
(cdkDropListDropped)="drop($event)"
|
||||||
|
@ -32,6 +32,9 @@ describe('DragDropSelectComponent', () => {
|
|||||||
{ id: '2', name: 'Item 2' },
|
{ id: '2', name: 'Item 2' },
|
||||||
{ id: '3', name: 'Item 3' },
|
{ id: '3', name: 'Item 3' },
|
||||||
])
|
])
|
||||||
|
|
||||||
|
component.writeValue(null)
|
||||||
|
expect(component.selectedItems).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should update selectedItems when an item is dropped within selectedList', () => {
|
it('should update selectedItems when an item is dropped within selectedList', () => {
|
||||||
|
@ -34,9 +34,8 @@ export class DragDropSelectComponent extends AbstractInputComponent<string[]> {
|
|||||||
|
|
||||||
writeValue(newValue: string[]): void {
|
writeValue(newValue: string[]): void {
|
||||||
super.writeValue(newValue)
|
super.writeValue(newValue)
|
||||||
this.selectedItems = newValue.map((id) =>
|
this.selectedItems =
|
||||||
this.items.find((i) => i.id === id)
|
newValue?.map((id) => this.items.find((i) => i.id === id)) ?? []
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public drop(event: CdkDragDrop<string[]>) {
|
public drop(event: CdkDragDrop<string[]>) {
|
||||||
|
@ -9,19 +9,19 @@
|
|||||||
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (documents.length && (!savedView.display_mode || savedView.display_mode === DashboardViewMode.TABLE)) {
|
@if (documents.length && displayMode === DisplayMode.TABLE) {
|
||||||
<table content class="table table-hover mb-0 mt-n2 align-middle">
|
<table content class="table table-hover mb-0 mt-n2 align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@for (column of savedView.display_fields; track column; let i = $index) {
|
@for (field of displayFields; track field; let i = $index) {
|
||||||
@if (activeDisplayFields.includes(column)) {
|
@if (displayFields.includes(field)) {
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
[ngClass]="{
|
[ngClass]="{
|
||||||
'd-none d-md-table-cell': i > 1,
|
'd-none d-md-table-cell': i > 1,
|
||||||
'w-25': column === DashboardViewTableColumn.CREATED || column === DashboardViewTableColumn.ADDED
|
'w-25': field === DisplayField.CREATED || field === DisplayField.ADDED
|
||||||
}">
|
}">
|
||||||
{{ getColumnTitle(column) }}
|
{{ getColumnTitle(field) }}
|
||||||
</th>
|
</th>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -30,44 +30,44 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
@for (doc of documents; track doc.id) {
|
@for (doc of documents; track doc.id) {
|
||||||
<tr>
|
<tr>
|
||||||
@for (column of savedView.display_fields; track column; let i = $index) {
|
@for (field of displayFields; track field; let i = $index) {
|
||||||
@if (activeDisplayFields.includes(column)) {
|
@if (displayFields.includes(field)) {
|
||||||
<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 (field) {
|
||||||
@case (DashboardViewTableColumn.ADDED) {
|
@case (DisplayField.ADDED) {
|
||||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.added | customDate}}</a>
|
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.added | customDate}}</a>
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.CREATED) {
|
@case (DisplayField.CREATED) {
|
||||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a>
|
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.created_date | customDate}}</a>
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.TITLE) {
|
@case (DisplayField.TITLE) {
|
||||||
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.CORRESPONDENT) {
|
@case (DisplayField.CORRESPONDENT) {
|
||||||
@if (doc.correspondent) {
|
@if (doc.correspondent) {
|
||||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)">{{(doc.correspondent$ | async)?.name}}</a>
|
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)">{{(doc.correspondent$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.TAGS) {
|
@case (DisplayField.TAGS) {
|
||||||
@for (t of doc.tags$ | async; track t) {
|
@for (t of doc.tags$ | async; track t) {
|
||||||
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)"></pngx-tag>
|
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)"></pngx-tag>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.DOCUMENT_TYPE) {
|
@case (DisplayField.DOCUMENT_TYPE) {
|
||||||
@if (doc.document_type) {
|
@if (doc.document_type) {
|
||||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)">{{(doc.document_type$ | async)?.name}}</a>
|
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)">{{(doc.document_type$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@case (DashboardViewTableColumn.STORAGE_PATH) {
|
@case (DisplayField.STORAGE_PATH) {
|
||||||
@if (doc.storage_path) {
|
@if (doc.storage_path) {
|
||||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)">{{(doc.storage_path$ | async)?.name}}</a>
|
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)">{{(doc.storage_path$ | async)?.name}}</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@if (column.startsWith(DashboardViewTableColumn.CUSTOM_FIELD)) {
|
@if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
|
||||||
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="column"></pngx-custom-field-display>
|
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
|
||||||
}
|
}
|
||||||
@if (i === savedView.display_fields.length - 1) {
|
@if (i === displayFields.length - 1) {
|
||||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||||
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
|
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
|
||||||
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
||||||
@ -89,14 +89,14 @@
|
|||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
} @else if (documents.length && savedView.display_mode === DashboardViewMode.SMALL_CARDS) {
|
} @else if (documents.length && displayMode === DisplayMode.SMALL_CARDS) {
|
||||||
<div class="row row-cols-paperless-cards my-n2">
|
<div class="row row-cols-paperless-cards my-n2">
|
||||||
@for (d of documents; track d.id) {
|
@for (d of documents; track d.id) {
|
||||||
<pngx-document-card-small
|
<pngx-document-card-small
|
||||||
class="p-0"
|
class="p-0"
|
||||||
(dblClickDocument)="openDocumentDetail(d)"
|
(dblClickDocument)="openDocumentDetail(d)"
|
||||||
[document]="d"
|
[document]="d"
|
||||||
[displayFields]="activeDisplayFields"
|
[displayFields]="displayFields"
|
||||||
(clickTag)="clickTag($event)"
|
(clickTag)="clickTag($event)"
|
||||||
(clickCorrespondent)="clickCorrespondent($event)"
|
(clickCorrespondent)="clickCorrespondent($event)"
|
||||||
(clickStoragePath)="clickStoragePath($event)"
|
(clickStoragePath)="clickStoragePath($event)"
|
||||||
@ -104,13 +104,13 @@
|
|||||||
</pngx-document-card-small>
|
</pngx-document-card-small>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} @else if (documents.length && savedView.display_mode === DashboardViewMode.LARGE_CARDS) {
|
} @else if (documents.length && displayMode === DisplayMode.LARGE_CARDS) {
|
||||||
<div class="row my-n2">
|
<div class="row my-n2">
|
||||||
@for (d of documents; track d.id) {
|
@for (d of documents; track d.id) {
|
||||||
<pngx-document-card-large
|
<pngx-document-card-large
|
||||||
(dblClickDocument)="openDocumentDetail(d)"
|
(dblClickDocument)="openDocumentDetail(d)"
|
||||||
[document]="d"
|
[document]="d"
|
||||||
[displayFields]="activeDisplayFields"
|
[displayFields]="displayFields"
|
||||||
(clickTag)="clickTag($event)"
|
(clickTag)="clickTag($event)"
|
||||||
(clickCorrespondent)="clickCorrespondent($event)"
|
(clickCorrespondent)="clickCorrespondent($event)"
|
||||||
(clickStoragePath)="clickStoragePath($event)"
|
(clickStoragePath)="clickStoragePath($event)"
|
||||||
|
@ -342,4 +342,39 @@ describe('SavedViewWidgetComponent', () => {
|
|||||||
'Storage path'
|
'Storage path'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should hide fields if no perms', () => {
|
||||||
|
fixture = TestBed.createComponent(SavedViewWidgetComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
component.savedView = {
|
||||||
|
sort_field: 'added',
|
||||||
|
sort_reverse: true,
|
||||||
|
show_in_sidebar: true,
|
||||||
|
show_on_dashboard: true,
|
||||||
|
filter_rules: [],
|
||||||
|
display_fields: [DisplayField.TITLE, 'foo' as any, 'bar' as any],
|
||||||
|
}
|
||||||
|
component.ngOnInit()
|
||||||
|
expect(component.displayFields).toEqual([DisplayField.TITLE])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should use fallback display settings', () => {
|
||||||
|
fixture = TestBed.createComponent(SavedViewWidgetComponent)
|
||||||
|
component = fixture.componentInstance
|
||||||
|
component.savedView = {
|
||||||
|
sort_field: 'added',
|
||||||
|
sort_reverse: true,
|
||||||
|
show_in_sidebar: true,
|
||||||
|
show_on_dashboard: true,
|
||||||
|
filter_rules: [],
|
||||||
|
}
|
||||||
|
component.ngOnInit()
|
||||||
|
expect(component.displayMode).toEqual(DisplayMode.TABLE)
|
||||||
|
expect(component.displayFields).toEqual([
|
||||||
|
DisplayField.CREATED,
|
||||||
|
DisplayField.TITLE,
|
||||||
|
DisplayField.TAGS,
|
||||||
|
DisplayField.CORRESPONDENT,
|
||||||
|
])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -46,8 +46,8 @@ export class SavedViewWidgetComponent
|
|||||||
extends ComponentWithPermissions
|
extends ComponentWithPermissions
|
||||||
implements OnInit, OnDestroy
|
implements OnInit, OnDestroy
|
||||||
{
|
{
|
||||||
public DashboardViewMode = DisplayMode
|
public DisplayMode = DisplayMode
|
||||||
public DashboardViewTableColumn = DisplayField
|
public DisplayField = DisplayField
|
||||||
public CustomFieldDataType = CustomFieldDataType
|
public CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
loading: boolean = true
|
loading: boolean = true
|
||||||
@ -80,14 +80,18 @@ export class SavedViewWidgetComponent
|
|||||||
mouseOnPreview = false
|
mouseOnPreview = false
|
||||||
popoverHidden = true
|
popoverHidden = true
|
||||||
|
|
||||||
activeDisplayFields: DisplayField[] = [
|
displayMode: DisplayMode
|
||||||
DisplayField.TITLE,
|
|
||||||
|
displayFields: DisplayField[] = [
|
||||||
DisplayField.CREATED,
|
DisplayField.CREATED,
|
||||||
DisplayField.ADDED,
|
DisplayField.TITLE,
|
||||||
|
DisplayField.TAGS,
|
||||||
|
DisplayField.CORRESPONDENT,
|
||||||
]
|
]
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.reload()
|
this.reload()
|
||||||
|
this.displayMode = this.savedView.display_mode ?? DisplayMode.TABLE
|
||||||
this.consumerStatusService
|
this.consumerStatusService
|
||||||
.onDocumentConsumptionFinished()
|
.onDocumentConsumptionFinished()
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
@ -109,19 +113,32 @@ export class SavedViewWidgetComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.savedView.display_fields?.forEach((column) => {
|
if (this.savedView.display_fields) {
|
||||||
|
this.displayFields = this.savedView.display_fields
|
||||||
|
?.map((field) => {
|
||||||
|
if (
|
||||||
|
[
|
||||||
|
DisplayField.TITLE,
|
||||||
|
DisplayField.CREATED,
|
||||||
|
DisplayField.ADDED,
|
||||||
|
].includes(field)
|
||||||
|
) {
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
let type: PermissionType = Object.values(PermissionType).find((t) =>
|
let type: PermissionType = Object.values(PermissionType).find((t) =>
|
||||||
t.includes(column)
|
t.includes(field)
|
||||||
)
|
)
|
||||||
if (column.startsWith(DisplayField.CUSTOM_FIELD)) {
|
if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
|
||||||
type = PermissionType.CustomField
|
type = PermissionType.CustomField
|
||||||
}
|
}
|
||||||
if (
|
return type &&
|
||||||
type &&
|
|
||||||
this.permissionsService.currentUserCan(PermissionAction.View, type)
|
this.permissionsService.currentUserCan(PermissionAction.View, type)
|
||||||
)
|
? field
|
||||||
this.activeDisplayFields.push(column)
|
: null
|
||||||
})
|
})
|
||||||
|
.filter((f) => f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
@ -102,9 +102,11 @@ export class DocumentListComponent
|
|||||||
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
this.unmodifiedSavedView.sort_reverse !== this.list.sortReverse ||
|
||||||
(this.unmodifiedSavedView.page_size &&
|
(this.unmodifiedSavedView.page_size &&
|
||||||
this.unmodifiedSavedView.page_size !== this.list.pageSize) ||
|
this.unmodifiedSavedView.page_size !== this.list.pageSize) ||
|
||||||
this.unmodifiedSavedView.display_mode !== this.list.displayMode ||
|
(this.unmodifiedSavedView.display_mode &&
|
||||||
|
this.unmodifiedSavedView.display_mode !== this.list.displayMode) ||
|
||||||
|
(this.unmodifiedSavedView.display_fields &&
|
||||||
this.unmodifiedSavedView.display_fields.join(',') !==
|
this.unmodifiedSavedView.display_fields.join(',') !==
|
||||||
this.activeDisplayFields.join(',') ||
|
this.activeDisplayFields.join(',')) ||
|
||||||
filterRulesDiffer(
|
filterRulesDiffer(
|
||||||
this.unmodifiedSavedView.filter_rules,
|
this.unmodifiedSavedView.filter_rules,
|
||||||
this.list.filterRules
|
this.list.filterRules
|
||||||
@ -190,12 +192,6 @@ export class DocumentListComponent
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!view.display_mode) {
|
|
||||||
view.display_mode = this.list.displayMode
|
|
||||||
}
|
|
||||||
if (!view.display_fields) {
|
|
||||||
view.display_fields = this.list.displayFields
|
|
||||||
}
|
|
||||||
this.unmodifiedSavedView = view
|
this.unmodifiedSavedView = view
|
||||||
this.list.activateSavedViewWithQueryParams(
|
this.list.activateSavedViewWithQueryParams(
|
||||||
view,
|
view,
|
||||||
|
@ -42,6 +42,7 @@ class Migration(migrations.Migration):
|
|||||||
model_name="savedview",
|
model_name="savedview",
|
||||||
name="display_fields",
|
name="display_fields",
|
||||||
field=multiselectfield.db.fields.MultiSelectField(
|
field=multiselectfield.db.fields.MultiSelectField(
|
||||||
|
blank=True,
|
||||||
choices=[
|
choices=[
|
||||||
("title", "Title"),
|
("title", "Title"),
|
||||||
("created", "Created"),
|
("created", "Created"),
|
||||||
@ -51,8 +52,9 @@ class Migration(migrations.Migration):
|
|||||||
("correspondent", "Correspondent"),
|
("correspondent", "Correspondent"),
|
||||||
("storagepath", "Storage Path"),
|
("storagepath", "Storage Path"),
|
||||||
],
|
],
|
||||||
default="created,title,tag,correspondent",
|
|
||||||
max_length=128,
|
max_length=128,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Document display fields",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -606,7 +606,8 @@ class SavedView(ModelWithOwner):
|
|||||||
verbose_name=_("Document display fields"),
|
verbose_name=_("Document display fields"),
|
||||||
choices=DisplayFields.choices,
|
choices=DisplayFields.choices,
|
||||||
dyanmic_choices=[DynamicDisplayFields.CUSTOM_FIELD],
|
dyanmic_choices=[DynamicDisplayFields.CUSTOM_FIELD],
|
||||||
default=f"{DisplayFields.CREATED},{DisplayFields.TITLE},{DisplayFields.TAGS},{DisplayFields.CORRESPONDENT}",
|
null=True,
|
||||||
|
blank=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user