From 7e3ba86ccc54f28e5dc056883fbda02dd07067f9 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 23 Oct 2019 17:04:23 +0200 Subject: [PATCH] 65717: Edit-bitstreams fetch all bundles and expandable bitstream list --- resources/i18n/en.json5 | 4 ++ .../item-bitstreams.component.ts | 12 +++- .../item-edit-bitstream-bundle.component.html | 17 +++++ .../item-edit-bitstream-bundle.component.ts | 63 ++++++++++++++++++- src/app/core/data/bundle-data.service.ts | 27 +++++++- src/app/core/data/item-data.service.ts | 15 ++--- .../object-updates/object-updates.service.ts | 14 +++-- src/app/shared/utils/object-values-pipe.ts | 6 +- 8 files changed, 138 insertions(+), 20 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 9a1d41e0ba..195b6fd72b 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -278,6 +278,9 @@ "item.bitstreams.upload.title": "Upload bitstream", "item.edit.bitstreams.bundle.edit.buttons.upload": "Upload", + "item.edit.bitstreams.bundle.displaying": "Currently displaying {{ amount }} bitstreams of {{ total }}.", + "item.edit.bitstreams.bundle.load.all": "Load all ({{ total }})", + "item.edit.bitstreams.bundle.load.more": "Load more", "item.edit.bitstreams.bundle.name": "BUNDLE: {{ name }}", "item.edit.bitstreams.discard-button": "Discard", "item.edit.bitstreams.edit.buttons.download": "Download", @@ -490,6 +493,7 @@ "journalvolume.page.volume": "Volume", "loading.bitstream": "Loading bitstream...", + "loading.bitstreams": "Loading bitstreams...", "loading.browse-by": "Loading items...", "loading.browse-by-page": "Loading page...", "loading.collection": "Loading collection...", diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts index 137a6d7178..7644479465 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts @@ -20,6 +20,7 @@ import { Item } from '../../../core/shared/item.model'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { Bundle } from '../../../core/shared/bundle.model'; +import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model'; @Component({ selector: 'ds-item-bitstreams', @@ -36,6 +37,15 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme */ bundles$: Observable; + /** + * The page options to use for fetching the bundles + */ + bundlesOptions = { + id: 'bundles-pagination-options', + currentPage: 1, + pageSize: 9999 + } as any; + /** * A subscription that checks when the item is deleted in cache and reloads the item by sending a new request * This is used to update the item in cache after bitstreams are deleted @@ -70,7 +80,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme * Actions to perform after the item has been initialized */ postItemInit(): void { - this.bundles$ = this.item.bundles.pipe( + this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({pagination: this.bundlesOptions})).pipe( getSucceededRemoteData(), getRemoteDataPayload(), map((bundlePage: PaginatedList) => bundlePage.page) diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html index 8fd7950176..3c0bce24d9 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.html @@ -28,5 +28,22 @@ + +
+ +
+ {{'item.edit.bitstreams.bundle.displaying' | translate:{ amount: bitstreamsRD?.payload?.elementsPerPage, total: bitstreamsRD?.payload?.totalElements } }} +
+ +
+ {{'loading.bitstreams' | translate}} +
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts index bc2483612f..0db3c73cf2 100644 --- a/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts +++ b/src/app/+item-page/edit-item-page/item-bitstreams/item-edit-bitstream-bundle/item-edit-bitstream-bundle.component.ts @@ -4,10 +4,16 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec import { Observable } from 'rxjs/internal/Observable'; import { FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer'; import { toBitstreamsArray } from '../../../../core/shared/item-bitstreams-utils'; -import { switchMap, tap } from 'rxjs/operators'; +import { map, switchMap, tap } from 'rxjs/operators'; import { Bitstream } from '../../../../core/shared/bitstream.model'; import { Item } from '../../../../core/shared/item.model'; import { CdkDragDrop } from '@angular/cdk/drag-drop'; +import { RemoteData } from '../../../../core/data/remote-data'; +import { PaginatedList } from '../../../../core/data/paginated-list'; +import { BundleDataService } from '../../../../core/data/bundle-data.service'; +import { PaginatedSearchOptions } from '../../../../+search-page/paginated-search-options.model'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { combineLatest as observableCombineLatest } from 'rxjs'; @Component({ selector: 'ds-item-edit-bitstream-bundle', @@ -39,21 +45,60 @@ export class ItemEditBitstreamBundleComponent implements OnInit { */ @Input() url: string; + /** + * The bitstreams within this bundle retrieved from the REST API + */ + bitstreamsRD$: Observable>>; + /** * The updates to the current bundle */ updates$: Observable; + /** + * The amount of one bitstreams one "batch" resembles + * The user is able to increase the amount of bitstreams displayed per bundle by this batch size until all are shown + */ + batchSize = 10; + + /** + * The page options to use for fetching the bitstreams + */ + bitstreamsOptions = { + id: 'bitstreams-pagination-options', + currentPage: 1, + pageSize: this.batchSize + } as any; + + /** + * The current amount of bitstreams to display for this bundle + * Starts off with just one batch + */ + currentSize$ = new BehaviorSubject(this.batchSize); + + /** + * Are we currently loading more bitstreams? + */ + isLoadingMore$: Observable; + constructor(private objectUpdatesService: ObjectUpdatesService, + private bundleService: BundleDataService, private viewContainerRef: ViewContainerRef) { } ngOnInit(): void { - this.updates$ = this.bundle.bitstreams.pipe( + this.bitstreamsRD$ = this.currentSize$.pipe( + switchMap((size: number) => this.bundleService.getBitstreams(this.bundle.id, + new PaginatedSearchOptions({pagination: Object.assign({}, this.bitstreamsOptions, { pageSize: size })}))) + ); + this.updates$ = this.bitstreamsRD$.pipe( toBitstreamsArray(), tap((bitstreams: Bitstream[]) => this.objectUpdatesService.initialize(this.bundle.self, bitstreams, new Date(), true)), switchMap((bitstreams: Bitstream[]) => this.objectUpdatesService.getFieldUpdatesByCustomOrder(this.bundle.self, bitstreams)) ); + this.isLoadingMore$ = observableCombineLatest(this.currentSize$, this.bitstreamsRD$).pipe( + map(([size, bitstreamsRD]: [number, RemoteData>]) => size > bitstreamsRD.payload.page.length) + ); this.viewContainerRef.createEmbeddedView(this.bundleView); } @@ -65,4 +110,18 @@ export class ItemEditBitstreamBundleComponent implements OnInit { drop(event: CdkDragDrop) { this.objectUpdatesService.saveMoveFieldUpdate(this.bundle.self, event.previousIndex, event.currentIndex); } + + /** + * Load more bitstreams (current size + batchSize) + */ + loadMore() { + this.currentSize$.next(this.currentSize$.value + this.batchSize); + } + + /** + * Load all bitstreams + */ + loadAll() { + this.currentSize$.next(9999); + } } diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index c3857b5877..abe366ee41 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -11,9 +11,13 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { FindAllOptions } from './request.models'; +import { FindAllOptions, GetRequest } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; -import { switchMap } from 'rxjs/operators'; +import { map, switchMap, take } from 'rxjs/operators'; +import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; +import { RemoteData } from './remote-data'; +import { PaginatedList } from './paginated-list'; +import { Bitstream } from '../shared/bitstream.model'; /** * A service responsible for fetching/sending data from/to the REST API on the bundles endpoint @@ -55,4 +59,23 @@ export class BundleDataService extends DataService { switchMap((href: string) => this.halService.getEndpoint(this.bitstreamsEndpoint, `${href}/${bundleId}`)) ); } + + /** + * Get a bundle's bitstreams using paginated search options + * @param bundleId The bundle's ID + * @param searchOptions The search options to use + */ + getBitstreams(bundleId: string, searchOptions?: PaginatedSearchOptions): Observable>> { + const hrefObs = this.getBitstreamsEndpoint(bundleId).pipe( + map((href) => searchOptions ? searchOptions.toRestUrl(href) : href) + ); + hrefObs.pipe( + take(1) + ).subscribe((href) => { + const request = new GetRequest(this.requestService.generateRequestId(), href); + this.requestService.configure(request); + }); + + return this.rdbService.buildList(hrefObs); + } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index bb62d63c69..7b36e77d3b 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -40,6 +40,7 @@ import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model'; import { Bitstream } from '../shared/bitstream.model'; +import { Bundle } from '../shared/bundle.model'; @Injectable() export class ItemDataService extends DataService { @@ -214,22 +215,22 @@ export class ItemDataService extends DataService { } /** - * Get the endpoint for an item's bitstreams + * Get the endpoint for an item's bundles * @param itemId */ - public getBitstreamsEndpoint(itemId: string): Observable { + public getBundlesEndpoint(itemId: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( - switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`)) + switchMap((url: string) => this.halService.getEndpoint('bundles', `${url}/${itemId}`)) ); } /** - * Get an item's bitstreams using paginated search options + * Get an item's bundles using paginated search options * @param itemId The item's ID * @param searchOptions The search options to use */ - public getBitstreams(itemId: string, searchOptions?: PaginatedSearchOptions): Observable>> { - const hrefObs = this.getBitstreamsEndpoint(itemId).pipe( + public getBundles(itemId: string, searchOptions?: PaginatedSearchOptions): Observable>> { + const hrefObs = this.getBundlesEndpoint(itemId).pipe( map((href) => searchOptions ? searchOptions.toRestUrl(href) : href) ); hrefObs.pipe( @@ -239,7 +240,7 @@ export class ItemDataService extends DataService { this.requestService.configure(request); }); - return this.rdbService.buildList(hrefObs); + return this.rdbService.buildList(hrefObs); } /** diff --git a/src/app/core/data/object-updates/object-updates.service.ts b/src/app/core/data/object-updates/object-updates.service.ts index 34aa74c576..9c14748f7f 100644 --- a/src/app/core/data/object-updates/object-updates.service.ts +++ b/src/app/core/data/object-updates/object-updates.service.ts @@ -138,13 +138,15 @@ export class ObjectUpdatesService { const objectUpdates = this.getObjectEntry(url); return objectUpdates.pipe(map((objectEntry) => { const fieldUpdates: FieldUpdates = {}; - for (const uuid of objectEntry.customOrder.newOrder) { - let fieldUpdate = objectEntry.fieldUpdates[uuid]; - if (isEmpty(fieldUpdate)) { - const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid); - fieldUpdate = { field: identifiable, changeType: undefined }; + if (hasValue(objectEntry)) { + for (const uuid of objectEntry.customOrder.newOrder) { + let fieldUpdate = objectEntry.fieldUpdates[uuid]; + if (isEmpty(fieldUpdate)) { + const identifiable = initialFields.find((object: Identifiable) => object.uuid === uuid); + fieldUpdate = {field: identifiable, changeType: undefined}; + } + fieldUpdates[uuid] = fieldUpdate; } - fieldUpdates[uuid] = fieldUpdate; } return fieldUpdates; })) diff --git a/src/app/shared/utils/object-values-pipe.ts b/src/app/shared/utils/object-values-pipe.ts index 7686b290ff..5962aa9380 100644 --- a/src/app/shared/utils/object-values-pipe.ts +++ b/src/app/shared/utils/object-values-pipe.ts @@ -1,5 +1,5 @@ import { PipeTransform, Pipe } from '@angular/core'; -import { hasValue } from '../empty.util'; +import { hasValue, isNotEmpty } from '../empty.util'; @Pipe({name: 'dsObjectValues'}) /** @@ -13,7 +13,9 @@ export class ObjectValuesPipe implements PipeTransform { */ transform(value, args:string[]): any { const values = []; - Object.values(value).forEach((v) => values.push(v)); + if (isNotEmpty(value)) { + Object.values(value).forEach((v) => values.push(v)); + } return values; } }