mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Feature: customizable fields display for documents, saved views & dashboard widgets (#6439)
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
<div class="d-flex flex-row mt-2 align-items-center">
|
||||
<span class="me-2">{{title}}:</span>
|
||||
<div class="d-flex flex-row gap-2 w-100 mh-1" style="min-height: 1em;"
|
||||
cdkDropList #selectedList="cdkDropList"
|
||||
cdkDropListOrientation="horizontal"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
[cdkDropListConnectedTo]="[unselectedList]">
|
||||
@for (item of selectedItems; track item.id) {
|
||||
<div class="badge bg-primary" cdkDrag>{{item.name}}</div>
|
||||
}
|
||||
@if (selectedItems.length === 0) {
|
||||
<div class="badge bg-light fst-italic" i18n>{{emptyText}}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<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 mh-1" style="min-height: 1em;"
|
||||
cdkDropList #unselectedList="cdkDropList"
|
||||
cdkDropListOrientation="horizontal"
|
||||
(cdkDropListDropped)="drop($event)"
|
||||
[cdkDropListConnectedTo]="[selectedList]">
|
||||
@for (item of unselectedItems; track item.id) {
|
||||
<div class="badge bg-secondary opacity-50" cdkDrag>{{item.name}}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,7 @@
|
||||
.badge {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.d-flex {
|
||||
overflow-x: scroll;
|
||||
}
|
@@ -0,0 +1,102 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop'
|
||||
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { DragDropSelectComponent } from './drag-drop-select.component'
|
||||
|
||||
describe('DragDropSelectComponent', () => {
|
||||
let component: DragDropSelectComponent
|
||||
let fixture: ComponentFixture<DragDropSelectComponent>
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [DragDropModule, FormsModule],
|
||||
declarations: [DragDropSelectComponent],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(DragDropSelectComponent)
|
||||
component = fixture.componentInstance
|
||||
fixture.debugElement.injector.get(NG_VALUE_ACCESSOR)
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should update selectedItems when writeValue is called', () => {
|
||||
const newValue = ['1', '2', '3']
|
||||
component.items = [
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
]
|
||||
component.writeValue(newValue)
|
||||
expect(component.selectedItems).toEqual([
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
])
|
||||
|
||||
component.writeValue(null)
|
||||
expect(component.selectedItems).toEqual([])
|
||||
})
|
||||
|
||||
it('should update selectedItems when an item is dropped within selectedList', () => {
|
||||
component.items = [
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
{ id: '4', name: 'Item 4' },
|
||||
]
|
||||
component.writeValue(['1', '2', '3'])
|
||||
const event = {
|
||||
previousContainer: component.selectedList,
|
||||
container: component.selectedList,
|
||||
previousIndex: 1,
|
||||
currentIndex: 2,
|
||||
}
|
||||
component.drop(event as any)
|
||||
expect(component.selectedItems).toEqual([
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
])
|
||||
})
|
||||
|
||||
it('should update selectedItems when an item is dropped from unselectedList to selectedList', () => {
|
||||
component.items = [
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
]
|
||||
component.writeValue(['1', '2'])
|
||||
const event = {
|
||||
previousContainer: component.unselectedList,
|
||||
container: component.selectedList,
|
||||
previousIndex: 0,
|
||||
currentIndex: 2,
|
||||
}
|
||||
component.drop(event as any)
|
||||
expect(component.selectedItems).toEqual([
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
])
|
||||
})
|
||||
|
||||
it('should update selectedItems when an item is dropped from selectedList to unselectedList', () => {
|
||||
component.items = [
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '2', name: 'Item 2' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
]
|
||||
component.writeValue(['1', '2', '3'])
|
||||
const event = {
|
||||
previousContainer: component.selectedList,
|
||||
container: component.unselectedList,
|
||||
previousIndex: 1,
|
||||
currentIndex: 0,
|
||||
}
|
||||
component.drop(event as any)
|
||||
expect(component.selectedItems).toEqual([
|
||||
{ id: '1', name: 'Item 1' },
|
||||
{ id: '3', name: 'Item 3' },
|
||||
])
|
||||
})
|
||||
})
|
@@ -0,0 +1,68 @@
|
||||
import { Component, Input, ViewChild, forwardRef } from '@angular/core'
|
||||
import { NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { AbstractInputComponent } from '../abstract-input'
|
||||
import {
|
||||
CdkDragDrop,
|
||||
CdkDropList,
|
||||
moveItemInArray,
|
||||
} from '@angular/cdk/drag-drop'
|
||||
|
||||
@Component({
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => DragDropSelectComponent),
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
selector: 'pngx-input-drag-drop-select',
|
||||
templateUrl: './drag-drop-select.component.html',
|
||||
styleUrl: './drag-drop-select.component.scss',
|
||||
})
|
||||
export class DragDropSelectComponent extends AbstractInputComponent<string[]> {
|
||||
@Input() title: string = $localize`Selected items`
|
||||
|
||||
@Input() items: { id: string; name: string }[] = []
|
||||
public selectedItems: { id: string; name: string }[] = []
|
||||
|
||||
@Input()
|
||||
emptyText = $localize`No items selected`
|
||||
|
||||
@ViewChild('selectedList') selectedList: CdkDropList
|
||||
@ViewChild('unselectedList') unselectedList: CdkDropList
|
||||
|
||||
get unselectedItems(): { id: string; name: string }[] {
|
||||
return this.items.filter((i) => !this.selectedItems.includes(i))
|
||||
}
|
||||
|
||||
writeValue(newValue: string[]): void {
|
||||
super.writeValue(newValue)
|
||||
this.selectedItems =
|
||||
newValue?.map((id) => this.items.find((i) => i.id === id)) ?? []
|
||||
}
|
||||
|
||||
public drop(event: CdkDragDrop<string[]>) {
|
||||
if (
|
||||
event.previousContainer === event.container &&
|
||||
event.container === this.selectedList
|
||||
) {
|
||||
moveItemInArray(
|
||||
this.selectedItems,
|
||||
event.previousIndex,
|
||||
event.currentIndex
|
||||
)
|
||||
} else if (event.container === this.selectedList) {
|
||||
this.selectedItems.splice(
|
||||
event.currentIndex,
|
||||
0,
|
||||
this.unselectedItems[event.previousIndex]
|
||||
)
|
||||
} else if (
|
||||
event.container === this.unselectedList &&
|
||||
event.previousContainer === this.selectedList
|
||||
) {
|
||||
this.selectedItems.splice(event.previousIndex, 1)
|
||||
}
|
||||
this.onChange(this.selectedItems.map((i) => i.id))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user