mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
118219: Remove unused paginated-drag-and-drop components
This commit is contained in:
@@ -26,8 +26,6 @@ import { ItemMoveComponent } from './item-move/item-move.component';
|
|||||||
import { ItemEditBitstreamBundleComponent } from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component';
|
import { ItemEditBitstreamBundleComponent } from './item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component';
|
||||||
import { BundleDataService } from '../../core/data/bundle-data.service';
|
import { BundleDataService } from '../../core/data/bundle-data.service';
|
||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
import { ItemEditBitstreamDragHandleComponent } from './item-bitstreams/item-edit-bitstream-drag-handle/item-edit-bitstream-drag-handle.component';
|
|
||||||
import { PaginatedDragAndDropBitstreamListComponent } from './item-bitstreams/item-edit-bitstream-bundle/paginated-drag-and-drop-bitstream-list/paginated-drag-and-drop-bitstream-list.component';
|
|
||||||
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
import { VirtualMetadataComponent } from './virtual-metadata/virtual-metadata.component';
|
||||||
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
import { ItemVersionHistoryComponent } from './item-version-history/item-version-history.component';
|
||||||
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
import { ItemAuthorizationsComponent } from './item-authorizations/item-authorizations.component';
|
||||||
@@ -82,12 +80,10 @@ import {
|
|||||||
ItemVersionHistoryComponent,
|
ItemVersionHistoryComponent,
|
||||||
ItemEditBitstreamComponent,
|
ItemEditBitstreamComponent,
|
||||||
ItemEditBitstreamBundleComponent,
|
ItemEditBitstreamBundleComponent,
|
||||||
PaginatedDragAndDropBitstreamListComponent,
|
|
||||||
EditRelationshipComponent,
|
EditRelationshipComponent,
|
||||||
EditRelationshipListComponent,
|
EditRelationshipListComponent,
|
||||||
ItemCollectionMapperComponent,
|
ItemCollectionMapperComponent,
|
||||||
ItemMoveComponent,
|
ItemMoveComponent,
|
||||||
ItemEditBitstreamDragHandleComponent,
|
|
||||||
VirtualMetadataComponent,
|
VirtualMetadataComponent,
|
||||||
ItemAuthorizationsComponent,
|
ItemAuthorizationsComponent,
|
||||||
IdentifierDataComponent,
|
IdentifierDataComponent,
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
<ds-pagination *ngIf="(objectsRD$ | async)?.payload"
|
|
||||||
[hideGear]="true"
|
|
||||||
[hidePagerWhenSinglePage]="true"
|
|
||||||
[hidePaginationDetail]="true"
|
|
||||||
[paginationOptions]="options"
|
|
||||||
[pageInfoState]="(objectsRD$ | async)?.payload"
|
|
||||||
[collectionSize]="(objectsRD$ | async)?.payload?.totalElements">
|
|
||||||
<ng-container *ngIf="!(loading$ | async)">
|
|
||||||
<div [id]="bundle.id" class="bundle-bitstreams-list"
|
|
||||||
[ngClass]="{'mb-3': (objectsRD$ | async)?.payload?.totalElements > pageSize}"
|
|
||||||
*ngVar="(updates$ | async) as updates" cdkDropList (cdkDropListDropped)="drop($event)">
|
|
||||||
<ng-container *ngIf="updates">
|
|
||||||
<div class="row bitstream-row" *ngFor="let uuid of customOrder" cdkDrag
|
|
||||||
[id]="uuid"
|
|
||||||
[ngClass]="{
|
|
||||||
'table-warning': updates[uuid].changeType === 0,
|
|
||||||
'table-danger': updates[uuid].changeType === 2,
|
|
||||||
'table-success': updates[uuid].changeType === 1,
|
|
||||||
'bg-white': updates[uuid].changeType === undefined
|
|
||||||
}">
|
|
||||||
<ds-item-edit-bitstream [fieldUpdate]="updates[uuid]"
|
|
||||||
[bundleUrl]="bundle.self"
|
|
||||||
[columnSizes]="columnSizes">
|
|
||||||
<div class="d-flex align-items-center bitstream-row-drag-handle" slot="drag-handle" cdkDragHandle tabindex="0">
|
|
||||||
<ds-item-edit-bitstream-drag-handle></ds-item-edit-bitstream-drag-handle>
|
|
||||||
</div>
|
|
||||||
</ds-item-edit-bitstream>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
<ds-themed-loading *ngIf="(loading$ | async)" [message]="'loading.bitstreams' | translate"></ds-themed-loading>
|
|
||||||
</ds-pagination>
|
|
@@ -1,150 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|
||||||
import { Bundle } from '../../../../../core/shared/bundle.model';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { PaginatedDragAndDropBitstreamListComponent } from './paginated-drag-and-drop-bitstream-list.component';
|
|
||||||
import { VarDirective } from '../../../../../shared/utils/var.directive';
|
|
||||||
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
|
|
||||||
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
|
|
||||||
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
|
|
||||||
import { Bitstream } from '../../../../../core/shared/bitstream.model';
|
|
||||||
import { BitstreamFormat } from '../../../../../core/shared/bitstream-format.model';
|
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
|
||||||
import { ResponsiveColumnSizes } from '../../../../../shared/responsive-table-sizes/responsive-column-sizes';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
|
||||||
import { createPaginatedList } from '../../../../../shared/testing/utils.test';
|
|
||||||
import { RequestService } from '../../../../../core/data/request.service';
|
|
||||||
import { PaginationService } from '../../../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationServiceStub } from '../../../../../shared/testing/pagination-service.stub';
|
|
||||||
|
|
||||||
describe('PaginatedDragAndDropBitstreamListComponent', () => {
|
|
||||||
let comp: PaginatedDragAndDropBitstreamListComponent;
|
|
||||||
let fixture: ComponentFixture<PaginatedDragAndDropBitstreamListComponent>;
|
|
||||||
let objectUpdatesService: ObjectUpdatesService;
|
|
||||||
let bundleService: BundleDataService;
|
|
||||||
let objectValuesPipe: ObjectValuesPipe;
|
|
||||||
let requestService: RequestService;
|
|
||||||
let paginationService;
|
|
||||||
|
|
||||||
const columnSizes = new ResponsiveTableSizes([
|
|
||||||
new ResponsiveColumnSizes(2, 2, 3, 4, 4),
|
|
||||||
new ResponsiveColumnSizes(2, 3, 3, 3, 3),
|
|
||||||
new ResponsiveColumnSizes(2, 2, 2, 2, 2),
|
|
||||||
new ResponsiveColumnSizes(6, 5, 4, 3, 3)
|
|
||||||
]);
|
|
||||||
|
|
||||||
const bundle = Object.assign(new Bundle(), {
|
|
||||||
id: 'bundle-1',
|
|
||||||
uuid: 'bundle-1',
|
|
||||||
_links: {
|
|
||||||
self: { href: 'bundle-1-selflink' }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const date = new Date();
|
|
||||||
const format = Object.assign(new BitstreamFormat(), {
|
|
||||||
shortDescription: 'PDF'
|
|
||||||
});
|
|
||||||
const bitstream1 = Object.assign(new Bitstream(), {
|
|
||||||
uuid: 'bitstreamUUID1',
|
|
||||||
name: 'Fake Bitstream 1',
|
|
||||||
bundleName: 'ORIGINAL',
|
|
||||||
description: 'Description',
|
|
||||||
format: createSuccessfulRemoteDataObject$(format)
|
|
||||||
});
|
|
||||||
const fieldUpdate1 = {
|
|
||||||
field: bitstream1,
|
|
||||||
changeType: undefined
|
|
||||||
};
|
|
||||||
const bitstream2 = Object.assign(new Bitstream(), {
|
|
||||||
uuid: 'bitstreamUUID2',
|
|
||||||
name: 'Fake Bitstream 2',
|
|
||||||
bundleName: 'ORIGINAL',
|
|
||||||
description: 'Description',
|
|
||||||
format: createSuccessfulRemoteDataObject$(format)
|
|
||||||
});
|
|
||||||
const fieldUpdate2 = {
|
|
||||||
field: bitstream2,
|
|
||||||
changeType: undefined
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
|
||||||
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
|
|
||||||
{
|
|
||||||
getFieldUpdates: observableOf({
|
|
||||||
[bitstream1.uuid]: fieldUpdate1,
|
|
||||||
[bitstream2.uuid]: fieldUpdate2,
|
|
||||||
}),
|
|
||||||
getFieldUpdatesExclusive: observableOf({
|
|
||||||
[bitstream1.uuid]: fieldUpdate1,
|
|
||||||
[bitstream2.uuid]: fieldUpdate2,
|
|
||||||
}),
|
|
||||||
getFieldUpdatesByCustomOrder: observableOf({
|
|
||||||
[bitstream1.uuid]: fieldUpdate1,
|
|
||||||
[bitstream2.uuid]: fieldUpdate2,
|
|
||||||
}),
|
|
||||||
saveMoveFieldUpdate: {},
|
|
||||||
saveRemoveFieldUpdate: {},
|
|
||||||
removeSingleFieldUpdate: {},
|
|
||||||
saveAddFieldUpdate: {},
|
|
||||||
discardFieldUpdates: {},
|
|
||||||
reinstateFieldUpdates: observableOf(true),
|
|
||||||
initialize: {},
|
|
||||||
getUpdatedFields: observableOf([bitstream1, bitstream2]),
|
|
||||||
getLastModified: observableOf(date),
|
|
||||||
hasUpdates: observableOf(true),
|
|
||||||
isReinstatable: observableOf(false),
|
|
||||||
isValidPage: observableOf(true),
|
|
||||||
initializeWithCustomOrder: {},
|
|
||||||
addPageToCustomOrder: {}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
bundleService = jasmine.createSpyObj('bundleService', {
|
|
||||||
getBitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream1, bitstream2])),
|
|
||||||
getBitstreamsEndpoint: observableOf('')
|
|
||||||
});
|
|
||||||
|
|
||||||
objectValuesPipe = new ObjectValuesPipe();
|
|
||||||
|
|
||||||
requestService = jasmine.createSpyObj('requestService', {
|
|
||||||
hasByHref$: observableOf(true)
|
|
||||||
});
|
|
||||||
|
|
||||||
paginationService = new PaginationServiceStub();
|
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [TranslateModule.forRoot()],
|
|
||||||
declarations: [PaginatedDragAndDropBitstreamListComponent, VarDirective],
|
|
||||||
providers: [
|
|
||||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
|
||||||
{ provide: BundleDataService, useValue: bundleService },
|
|
||||||
{ provide: ObjectValuesPipe, useValue: objectValuesPipe },
|
|
||||||
{ provide: RequestService, useValue: requestService },
|
|
||||||
{ provide: PaginationService, useValue: paginationService }
|
|
||||||
], schemas: [
|
|
||||||
NO_ERRORS_SCHEMA
|
|
||||||
]
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(PaginatedDragAndDropBitstreamListComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
comp.bundle = bundle;
|
|
||||||
comp.columnSizes = columnSizes;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize the objectsRD$', (done) => {
|
|
||||||
comp.objectsRD$.pipe(take(1)).subscribe((objects) => {
|
|
||||||
expect(objects.payload.page).toEqual([bitstream1, bitstream2]);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize the URL', () => {
|
|
||||||
expect(comp.url).toEqual(bundle.self);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,80 +0,0 @@
|
|||||||
import { AbstractPaginatedDragAndDropListComponent } from '../../../../../shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component';
|
|
||||||
import { Component, ElementRef, Input, OnInit } from '@angular/core';
|
|
||||||
import { Bundle } from '../../../../../core/shared/bundle.model';
|
|
||||||
import { Bitstream } from '../../../../../core/shared/bitstream.model';
|
|
||||||
import { ObjectUpdatesService } from '../../../../../core/data/object-updates/object-updates.service';
|
|
||||||
import { BundleDataService } from '../../../../../core/data/bundle-data.service';
|
|
||||||
import { switchMap } from 'rxjs/operators';
|
|
||||||
import { PaginatedSearchOptions } from '../../../../../shared/search/models/paginated-search-options.model';
|
|
||||||
import { ResponsiveTableSizes } from '../../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
|
||||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
|
||||||
import { ObjectValuesPipe } from '../../../../../shared/utils/object-values-pipe';
|
|
||||||
import { RequestService } from '../../../../../core/data/request.service';
|
|
||||||
import { PaginationService } from '../../../../../core/pagination/pagination.service';
|
|
||||||
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-paginated-drag-and-drop-bitstream-list',
|
|
||||||
styleUrls: ['../../item-bitstreams.component.scss'],
|
|
||||||
templateUrl: './paginated-drag-and-drop-bitstream-list.component.html',
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* A component listing edit-bitstream rows for each bitstream within the given bundle.
|
|
||||||
* This component makes use of the AbstractPaginatedDragAndDropListComponent, allowing for users to drag and drop
|
|
||||||
* bitstreams within the paginated list. To drag and drop a bitstream between two pages, drag the row on top of the
|
|
||||||
* page number you want the bitstream to end up at. Doing so will add the bitstream to the top of that page.
|
|
||||||
*/
|
|
||||||
// NOTE:
|
|
||||||
// This component was used by the item-edit-bitstream-bundle.component, but this is no longer the case. It is left here
|
|
||||||
// as a reference for the drag-and-drop functionality. This component (and the abstract version it extends) should be
|
|
||||||
// removed once this reference is no longer useful.
|
|
||||||
export class PaginatedDragAndDropBitstreamListComponent extends AbstractPaginatedDragAndDropListComponent<Bitstream> implements OnInit {
|
|
||||||
/**
|
|
||||||
* The bundle to display bitstreams for
|
|
||||||
*/
|
|
||||||
@Input() bundle: Bundle;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bootstrap sizes used for the columns within this table
|
|
||||||
*/
|
|
||||||
@Input() columnSizes: ResponsiveTableSizes;
|
|
||||||
|
|
||||||
constructor(protected objectUpdatesService: ObjectUpdatesService,
|
|
||||||
protected elRef: ElementRef,
|
|
||||||
protected objectValuesPipe: ObjectValuesPipe,
|
|
||||||
protected bundleService: BundleDataService,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected requestService: RequestService) {
|
|
||||||
super(objectUpdatesService, elRef, objectValuesPipe, paginationService);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
super.ngOnInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the bitstreams observable depending on currentPage$
|
|
||||||
*/
|
|
||||||
initializeObjectsRD(): void {
|
|
||||||
this.objectsRD$ = this.currentPage$.pipe(
|
|
||||||
switchMap((page: PaginationComponentOptions) => {
|
|
||||||
const paginatedOptions = new PaginatedSearchOptions({pagination: Object.assign({}, page)});
|
|
||||||
return this.bundleService.getBitstreamsEndpoint(this.bundle.id, paginatedOptions).pipe(
|
|
||||||
switchMap((href) => this.requestService.hasByHref$(href)),
|
|
||||||
switchMap(() => this.bundleService.getBitstreams(
|
|
||||||
this.bundle.id,
|
|
||||||
paginatedOptions,
|
|
||||||
followLink('format')
|
|
||||||
))
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the URL used for the field-update store, in this case the bundle's self-link
|
|
||||||
*/
|
|
||||||
initializeURL(): void {
|
|
||||||
this.url = this.bundle.self;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +0,0 @@
|
|||||||
<ng-template #handleView>
|
|
||||||
<div class="drag-handle text-muted float-left p-1 mr-2" tabindex="0">
|
|
||||||
<i class="fas fa-grip-vertical fa-fw" [title]="'item.edit.bitstreams.edit.buttons.drag' | translate"></i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
@@ -1,26 +0,0 @@
|
|||||||
import { Component, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-item-edit-bitstream-drag-handle',
|
|
||||||
styleUrls: ['../item-bitstreams.component.scss'],
|
|
||||||
templateUrl: './item-edit-bitstream-drag-handle.component.html',
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Component displaying a drag handle for the item-edit-bitstream page
|
|
||||||
* Creates an embedded view of the contents
|
|
||||||
* (which means it'll be added to the parents html without a wrapping ds-item-edit-bitstream-drag-handle element)
|
|
||||||
*/
|
|
||||||
export class ItemEditBitstreamDragHandleComponent implements OnInit {
|
|
||||||
/**
|
|
||||||
* The view on the drag-handle
|
|
||||||
*/
|
|
||||||
@ViewChild('handleView', {static: true}) handleView;
|
|
||||||
|
|
||||||
constructor(private viewContainerRef: ViewContainerRef) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.viewContainerRef.createEmbeddedView(this.handleView);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,136 +0,0 @@
|
|||||||
import { AbstractPaginatedDragAndDropListComponent } from './abstract-paginated-drag-and-drop-list.component';
|
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|
||||||
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
|
|
||||||
import { Component, ElementRef } from '@angular/core';
|
|
||||||
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
|
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
import { PaginationComponent } from '../pagination/pagination.component';
|
|
||||||
import { createSuccessfulRemoteDataObject } from '../remote-data.utils';
|
|
||||||
import { createPaginatedList } from '../testing/utils.test';
|
|
||||||
import { ObjectValuesPipe } from '../utils/object-values-pipe';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { PaginationServiceStub } from '../testing/pagination-service.stub';
|
|
||||||
import { FieldUpdates } from '../../core/data/object-updates/field-updates.model';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-mock-paginated-drag-drop-abstract',
|
|
||||||
template: ''
|
|
||||||
})
|
|
||||||
class MockAbstractPaginatedDragAndDropListComponent extends AbstractPaginatedDragAndDropListComponent<DSpaceObject> {
|
|
||||||
|
|
||||||
constructor(protected objectUpdatesService: ObjectUpdatesService,
|
|
||||||
protected elRef: ElementRef,
|
|
||||||
protected objectValuesPipe: ObjectValuesPipe,
|
|
||||||
protected mockUrl: string,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected mockObjectsRD$: Observable<RemoteData<PaginatedList<DSpaceObject>>>) {
|
|
||||||
super(objectUpdatesService, elRef, objectValuesPipe, paginationService);
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeObjectsRD(): void {
|
|
||||||
this.objectsRD$ = this.mockObjectsRD$;
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeURL(): void {
|
|
||||||
this.url = this.mockUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('AbstractPaginatedDragAndDropListComponent', () => {
|
|
||||||
let component: MockAbstractPaginatedDragAndDropListComponent;
|
|
||||||
let objectUpdatesService: ObjectUpdatesService;
|
|
||||||
let elRef: ElementRef;
|
|
||||||
let objectValuesPipe: ObjectValuesPipe;
|
|
||||||
|
|
||||||
const url = 'mock-abstract-paginated-drag-and-drop-list-component';
|
|
||||||
|
|
||||||
|
|
||||||
const object1 = Object.assign(new DSpaceObject(), { uuid: 'object-1' });
|
|
||||||
const object2 = Object.assign(new DSpaceObject(), { uuid: 'object-2' });
|
|
||||||
const objectsRD = createSuccessfulRemoteDataObject(createPaginatedList([object1, object2]));
|
|
||||||
let objectsRD$: BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>>;
|
|
||||||
let paginationService;
|
|
||||||
|
|
||||||
const updates = {
|
|
||||||
[object1.uuid]: { field: object1, changeType: undefined },
|
|
||||||
[object2.uuid]: { field: object2, changeType: undefined }
|
|
||||||
} as FieldUpdates;
|
|
||||||
|
|
||||||
let paginationComponent: PaginationComponent;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService', {
|
|
||||||
initialize: {},
|
|
||||||
getFieldUpdatesExclusive: observableOf(updates)
|
|
||||||
});
|
|
||||||
elRef = {
|
|
||||||
nativeElement: jasmine.createSpyObj('nativeElement', {
|
|
||||||
querySelector: {}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
objectValuesPipe = new ObjectValuesPipe();
|
|
||||||
paginationComponent = jasmine.createSpyObj('paginationComponent', {
|
|
||||||
doPageChange: {}
|
|
||||||
});
|
|
||||||
paginationService = new PaginationServiceStub();
|
|
||||||
objectsRD$ = new BehaviorSubject(objectsRD);
|
|
||||||
component = new MockAbstractPaginatedDragAndDropListComponent(objectUpdatesService, elRef, objectValuesPipe, url, paginationService, objectsRD$);
|
|
||||||
component.paginationComponent = paginationComponent;
|
|
||||||
component.ngOnInit();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call initialize to initialize the objects in the store', () => {
|
|
||||||
expect(objectUpdatesService.initialize).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should initialize the updates correctly', (done) => {
|
|
||||||
component.updates$.pipe(take(1)).subscribe((fieldUpdates) => {
|
|
||||||
expect(fieldUpdates).toEqual(updates);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('drop', () => {
|
|
||||||
const event = {
|
|
||||||
previousIndex: 0,
|
|
||||||
currentIndex: 1,
|
|
||||||
item: { element: { nativeElement: { id: object1.uuid } } }
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
describe('when the user is hovering over a new page', () => {
|
|
||||||
const hoverPage = 3;
|
|
||||||
const hoverElement = { textContent: '' + hoverPage };
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
elRef.nativeElement.querySelector.and.returnValue(hoverElement);
|
|
||||||
spyOn(component.dropObject, 'emit');
|
|
||||||
component.drop(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should send out a dropObject event with the expected processed paginated indexes', () => {
|
|
||||||
expect(component.dropObject.emit).toHaveBeenCalledWith(Object.assign({
|
|
||||||
fromIndex: ((component.currentPage$.value.currentPage - 1) * component.pageSize) + event.previousIndex,
|
|
||||||
toIndex: ((hoverPage - 1) * component.pageSize),
|
|
||||||
finish: jasmine.anything()
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the user is not hovering over a new page', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(component.dropObject, 'emit');
|
|
||||||
component.drop(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should send out a dropObject event with the expected properties', () => {
|
|
||||||
expect(component.dropObject.emit).toHaveBeenCalledWith(Object.assign({
|
|
||||||
fromIndex: event.previousIndex,
|
|
||||||
toIndex: event.currentIndex,
|
|
||||||
finish: jasmine.anything()
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,239 +0,0 @@
|
|||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
|
||||||
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
|
|
||||||
import { ObjectUpdatesService } from '../../core/data/object-updates/object-updates.service';
|
|
||||||
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
|
||||||
import { hasValue } from '../empty.util';
|
|
||||||
import {
|
|
||||||
paginatedListToArray,
|
|
||||||
getFirstSucceededRemoteData,
|
|
||||||
getAllSucceededRemoteData
|
|
||||||
} from '../../core/shared/operators';
|
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|
||||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
|
||||||
import { Component, ElementRef, EventEmitter, OnDestroy, Output, ViewChild } from '@angular/core';
|
|
||||||
import { PaginationComponent } from '../pagination/pagination.component';
|
|
||||||
import { ObjectValuesPipe } from '../utils/object-values-pipe';
|
|
||||||
import { compareArraysUsing } from '../../item-page/simple/item-types/shared/item-relationships-utils';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { FieldUpdate } from '../../core/data/object-updates/field-update.model';
|
|
||||||
import { FieldUpdates } from '../../core/data/object-updates/field-updates.model';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operator used for comparing {@link FieldUpdate}s by their field's UUID
|
|
||||||
*/
|
|
||||||
export const compareArraysUsingFieldUuids = () =>
|
|
||||||
compareArraysUsing((fieldUpdate: FieldUpdate) => (hasValue(fieldUpdate) && hasValue(fieldUpdate.field)) ? fieldUpdate.field.uuid : undefined);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstract component containing general methods and logic to be able to drag and drop objects within a paginated
|
|
||||||
* list. This implementation supports being able to drag and drop objects between pages.
|
|
||||||
* Dragging an object on top of a page number will automatically detect the page it's being dropped on and send a
|
|
||||||
* dropObject event to the parent component containing detailed information about the indexes the object was dropped from
|
|
||||||
* and to.
|
|
||||||
*
|
|
||||||
* To extend this component, it is important to make sure to:
|
|
||||||
* - Initialize objectsRD$ within the initializeObjectsRD() method
|
|
||||||
* - Initialize a unique URL for this component/page within the initializeURL() method
|
|
||||||
* - Add (cdkDropListDropped)="drop($event)" to the cdkDropList element in your template
|
|
||||||
* - Add (pageChange)="switchPage($event)" to the ds-pagination element in your template
|
|
||||||
* - Use the updates$ observable for building your list of cdkDrag elements in your template
|
|
||||||
*
|
|
||||||
* An example component extending from this abstract component: PaginatedDragAndDropBitstreamListComponent
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-paginated-drag-drop-abstract',
|
|
||||||
template: ''
|
|
||||||
})
|
|
||||||
export abstract class AbstractPaginatedDragAndDropListComponent<T extends DSpaceObject> implements OnDestroy {
|
|
||||||
/**
|
|
||||||
* A view on the child pagination component
|
|
||||||
*/
|
|
||||||
@ViewChild(PaginationComponent) paginationComponent: PaginationComponent;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an event when the user drops an object on the pagination
|
|
||||||
* The event contains details about the index the object came from and is dropped to (across the entirety of the list,
|
|
||||||
* not just within a single page)
|
|
||||||
*/
|
|
||||||
@Output() dropObject: EventEmitter<any> = new EventEmitter<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL to use for accessing the object updates from this list
|
|
||||||
*/
|
|
||||||
url: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The objects to retrieve data for and transform into field updates
|
|
||||||
*/
|
|
||||||
objectsRD$: Observable<RemoteData<PaginatedList<T>>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The updates to the current list
|
|
||||||
*/
|
|
||||||
updates$: Observable<FieldUpdates>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of object UUIDs
|
|
||||||
* This is the order the objects will be displayed in
|
|
||||||
*/
|
|
||||||
customOrder: string[];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The amount of objects to display per page
|
|
||||||
*/
|
|
||||||
pageSize = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The page options to use for fetching the objects
|
|
||||||
* Start at page 1 and always use the set page size
|
|
||||||
*/
|
|
||||||
options = Object.assign(new PaginationComponentOptions(),{
|
|
||||||
id: 'dad',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: this.pageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current page being displayed
|
|
||||||
*/
|
|
||||||
currentPage$ = new BehaviorSubject<PaginationComponentOptions>(this.options);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not we should display a loading animation
|
|
||||||
* This is used to display a loading page when the user drops a bitstream onto a new page. The loading animation
|
|
||||||
* should stop once the bitstream has moved to the new page and the new page's response has loaded and contains the
|
|
||||||
* dropped object on top (see this.stopLoadingWhenFirstIs below)
|
|
||||||
*/
|
|
||||||
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of subscriptions
|
|
||||||
*/
|
|
||||||
subs: Subscription[] = [];
|
|
||||||
|
|
||||||
protected constructor(protected objectUpdatesService: ObjectUpdatesService,
|
|
||||||
protected elRef: ElementRef,
|
|
||||||
protected objectValuesPipe: ObjectValuesPipe,
|
|
||||||
protected paginationService: PaginationService
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the observables
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
|
||||||
this.initializeObjectsRD();
|
|
||||||
this.initializeURL();
|
|
||||||
this.initializeUpdates();
|
|
||||||
this.initializePagination();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrite this method to define how the list of objects is initialized and updated
|
|
||||||
*/
|
|
||||||
abstract initializeObjectsRD(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrite this method to define how the URL is set
|
|
||||||
*/
|
|
||||||
abstract initializeURL(): void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the current pagination retrieval from the paginationService and push to the currentPage$
|
|
||||||
*/
|
|
||||||
initializePagination() {
|
|
||||||
this.paginationService.getCurrentPagination(this.options.id, this.options).subscribe((currentPagination) => {
|
|
||||||
this.currentPage$.next(currentPagination);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the field-updates in the store
|
|
||||||
*/
|
|
||||||
initializeUpdates(): void {
|
|
||||||
this.objectsRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
paginatedListToArray(),
|
|
||||||
).subscribe((objects: T[]) => {
|
|
||||||
this.objectUpdatesService.initialize(this.url, objects, new Date());
|
|
||||||
});
|
|
||||||
this.updates$ = this.objectsRD$.pipe(
|
|
||||||
getAllSucceededRemoteData(),
|
|
||||||
paginatedListToArray(),
|
|
||||||
switchMap((objects: T[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, objects))
|
|
||||||
);
|
|
||||||
this.subs.push(
|
|
||||||
this.updates$.pipe(
|
|
||||||
map((fieldUpdates) => this.objectValuesPipe.transform(fieldUpdates)),
|
|
||||||
distinctUntilChanged(compareArraysUsingFieldUuids())
|
|
||||||
).subscribe((updateValues) => {
|
|
||||||
this.customOrder = updateValues.map((fieldUpdate) => fieldUpdate.field.uuid);
|
|
||||||
// We received new values, stop displaying a loading indicator if it's present
|
|
||||||
this.loading$.next(false);
|
|
||||||
}),
|
|
||||||
// Disable the pagination when objects are loading
|
|
||||||
this.loading$.subscribe((loading) => this.options.disabled = loading)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An object was moved, send updates to the dropObject EventEmitter
|
|
||||||
* When the object is dropped on a page within the pagination of this component, the object moves to the top of that
|
|
||||||
* page and the pagination automatically loads and switches the view to that page (this is done by calling the event's
|
|
||||||
* finish() method after sending patch requests to the REST API)
|
|
||||||
* @param event
|
|
||||||
*/
|
|
||||||
drop(event: CdkDragDrop<any>) {
|
|
||||||
const dragIndex = event.previousIndex;
|
|
||||||
let dropIndex = event.currentIndex;
|
|
||||||
const dragPage = this.currentPage$.value.currentPage - 1;
|
|
||||||
let dropPage = this.currentPage$.value.currentPage - 1;
|
|
||||||
|
|
||||||
// Check if the user is hovering over any of the pagination's pages at the time of dropping the object
|
|
||||||
const droppedOnElement = this.elRef.nativeElement.querySelector('.page-item:hover');
|
|
||||||
if (hasValue(droppedOnElement) && hasValue(droppedOnElement.textContent)) {
|
|
||||||
// The user is hovering over a page, fetch the page's number from the element
|
|
||||||
const droppedPage = Number(droppedOnElement.textContent);
|
|
||||||
if (hasValue(droppedPage) && !Number.isNaN(droppedPage)) {
|
|
||||||
dropPage = droppedPage - 1;
|
|
||||||
dropIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isNewPage = dragPage !== dropPage;
|
|
||||||
// Move the object in the custom order array if the drop happened within the same page
|
|
||||||
// This allows us to instantly display a change in the order, instead of waiting for the REST API's response first
|
|
||||||
if (!isNewPage && dragIndex !== dropIndex) {
|
|
||||||
moveItemInArray(this.customOrder, dragIndex, dropIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const redirectPage = dropPage + 1;
|
|
||||||
const fromIndex = (dragPage * this.pageSize) + dragIndex;
|
|
||||||
const toIndex = (dropPage * this.pageSize) + dropIndex;
|
|
||||||
// Send out a drop event (and navigate to the new page) when the "from" and "to" indexes are different from each other
|
|
||||||
if (fromIndex !== toIndex) {
|
|
||||||
if (isNewPage) {
|
|
||||||
this.loading$.next(true);
|
|
||||||
}
|
|
||||||
this.dropObject.emit(Object.assign({
|
|
||||||
fromIndex,
|
|
||||||
toIndex,
|
|
||||||
finish: () => {
|
|
||||||
if (isNewPage) {
|
|
||||||
this.paginationComponent.doPageChange(redirectPage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* unsub all subscriptions
|
|
||||||
*/
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
|
||||||
this.paginationService.clearPagination(this.options.id);
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user