Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,4 +210,4 @@
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^4.5.0"
}
}
}
5 changes: 3 additions & 2 deletions src/app/item-page/edit-item-page/edit-item-page.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgbTooltipModule, NgbModule } from '@ng-bootstrap/ng-bootstrap';

import { SharedModule } from '../../shared/shared.module';
import { EditItemPageRoutingModule } from './edit-item-page.routing.module';
Expand Down Expand Up @@ -48,7 +48,8 @@ import { ResourcePoliciesModule } from '../../shared/resource-policies/resource-
EditItemPageRoutingModule,
SearchPageModule,
DragDropModule,
ResourcePoliciesModule
ResourcePoliciesModule,
NgbModule
],
declarations: [
EditItemPageComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
<div class="container">
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert>
<ds-resource-policies [resourceType]="'item'" [resourceUUID]="(getItemUUID() | async)"></ds-resource-policies>
<ng-container *ngFor="let bundle of (getItemBundles() | async); trackById">
<ds-resource-policies [resourceType]="'bundle'"
[resourceUUID]="bundle.id"></ds-resource-policies>
<ng-container *ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id) | async)?.page; trackById">
<ds-resource-policies [resourceType]="'bitstream'"
[resourceUUID]="bitstream.id"></ds-resource-policies>
<ds-resource-policies [resourceType]="'item'" [resourceName]="(getItemName() | async)"
[resourceUUID]="(getItemUUID() | async)">
</ds-resource-policies>
<ng-container *ngFor="let bundle of (bundles$ | async); trackById">
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">
</ds-resource-policies>
<ng-container *ngIf="(bundleBitstreamsMap.get(bundle.id)?.bitstreams | async)?.length > 0">
<div class="card auth-bitstream-container">
<div class="card-header">
<button type="button" class="btn btn-outline-primary" (click)="collapseArea(bundle.id)"
[attr.aria-expanded]="false" [attr.aria-controls]="bundle.id">
{{ 'collection.edit.item.authorizations.show-bitstreams-button' | translate }} {{bundle.name}}
</button>
</div>
<div class="card-body" [id]="bundle.id" [ngbCollapse]="bundleBitstreamsMap.get(bundle.id).isCollapsed">
<ng-container
*ngFor="let bitstream of (bundleBitstreamsMap.get(bundle.id).bitstreams | async); trackById">
<ds-resource-policies [resourceType]="'bitstream'" [resourceUUID]="bitstream.id"
[resourceName]="bitstream.name"></ds-resource-policies>
</ng-container>
<div class="row justify-content-center" *ngIf="!bundleBitstreamsMap.get(bundle.id).allBitstreamsLoaded">
<button type="button" class="btn btn-link" (click)="onBitstreamsLoad(bundle)">{{ 'collection.edit.item.authorizations.load-more-button' | translate }}</button>
</div>
</div>
</div>
</ng-container>
</ng-container>
<div class="row justify-content-center" *ngIf="!allBundlesLoaded">
<button type="button" class="btn btn-link" (click)="onBundleLoad()">{{ 'collection.edit.item.authorizations.load-bundle-button' | translate }}</button>
</div>
</div>

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.auth-bitstream-container {
margin-top: -1em;
margin-bottom: 1.5em;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Observable } from 'rxjs/internal/Observable';
import { waitForAsync, ComponentFixture, inject, TestBed } from '@angular/core/testing';
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { of as observableOf } from 'rxjs';
import { of as observableOf, of } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { cold } from 'jasmine-marbles';
import { ItemAuthorizationsComponent } from './item-authorizations.component';
import { ItemAuthorizationsComponent, BitstreamMapValue } from './item-authorizations.component';
import { Bitstream } from '../../../core/shared/bitstream.model';
import { Bundle } from '../../../core/shared/bundle.model';
import { Item } from '../../../core/shared/item.model';
Expand Down Expand Up @@ -57,8 +58,6 @@ describe('ItemAuthorizationsComponent test suite', () => {
bitstreams: createSuccessfulRemoteDataObject$(createPaginatedList([bitstream3, bitstream4]))
});
const bundles = [bundle1, bundle2];
const bitstreamList1: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream1, bitstream2]);
const bitstreamList2: PaginatedList<Bitstream> = buildPaginatedList(new PageInfo(), [bitstream3, bitstream4]);

const item = Object.assign(new Item(), {
uuid: 'item',
Expand Down Expand Up @@ -142,13 +141,12 @@ describe('ItemAuthorizationsComponent test suite', () => {
expect(compAsAny.bundleBitstreamsMap.has('bundle1')).toBeTruthy();
expect(compAsAny.bundleBitstreamsMap.has('bundle2')).toBeTruthy();
let bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle1');
expect(bitstreamList).toBeObservable(cold('(a|)', {
a: bitstreamList1
expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', {
a : [bitstream1, bitstream2]
}));

bitstreamList = compAsAny.bundleBitstreamsMap.get('bundle2');
expect(bitstreamList).toBeObservable(cold('(a|)', {
a: bitstreamList2
expect(bitstreamList.bitstreams).toBeObservable(cold('(a|)', {
a: [bitstream3, bitstream4]
}));
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isEqual } from 'lodash';
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

Expand All @@ -6,7 +8,8 @@ import { catchError, filter, first, map, mergeMap, take } from 'rxjs/operators';

import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
import {
getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteDataWithNotEmptyPayload,
getFirstSucceededRemoteDataPayload,
getFirstSucceededRemoteDataWithNotEmptyPayload,
} from '../../../core/shared/operators';
import { Item } from '../../../core/shared/item.model';
import { followLink } from '../../../shared/utils/follow-link-config.model';
Expand All @@ -25,7 +28,8 @@ interface BundleBitstreamsMapEntry {

@Component({
selector: 'ds-item-authorizations',
templateUrl: './item-authorizations.component.html'
templateUrl: './item-authorizations.component.html',
styleUrls:['./item-authorizations.component.scss']
})
/**
* Component that handles the item Authorizations
Expand All @@ -36,13 +40,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
* A map that contains all bitstream of the item's bundles
* @type {Observable<Map<string, Observable<PaginatedList<Bitstream>>>>}
*/
public bundleBitstreamsMap: Map<string, Observable<PaginatedList<Bitstream>>> = new Map<string, Observable<PaginatedList<Bitstream>>>();
public bundleBitstreamsMap: Map<string, BitstreamMapValue> = new Map<string, BitstreamMapValue>();

/**
* The list of bundle for the item
* The list of all bundles for the item
* @type {Observable<PaginatedList<Bundle>>}
*/
private bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);
bundles$: BehaviorSubject<Bundle[]> = new BehaviorSubject<Bundle[]>([]);

/**
* The target editing item
Expand All @@ -56,32 +60,102 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
*/
private subs: Subscription[] = [];

/**
* The size of the bundles to be loaded on demand
* @type {number}
*/
bundlesPerPage = 6;

/**
* The number of current page
* @type {number}
*/
bundlesPageSize = 1;

/**
* The flag to show or not the 'Load more' button
* based on the condition if all the bundles are loaded or not
* @type {boolean}
*/
allBundlesLoaded = false;

/**
* Initial size of loaded bitstreams
* The size of incrementation used in bitstream pagination
*/
bitstreamSize = 4;

/**
* The size of the loaded bitstremas at a certain moment
* @private
*/
private bitstreamPageSize = 4;

/**
* Initialize instance variables
*
* @param {LinkService} linkService
* @param {ActivatedRoute} route
* @param nameService
*/
constructor(
private linkService: LinkService,
private route: ActivatedRoute
private route: ActivatedRoute,
private nameService: DSONameService
) {
}

/**
* Initialize the component, setting up the bundle and bitstream within the item
*/
ngOnInit(): void {
this.getBundlesPerItem();
}

/**
* Return the item's UUID
*/
getItemUUID(): Observable<string> {
return this.item$.pipe(
map((item: Item) => item.id),
first((UUID: string) => isNotEmpty(UUID))
);
}

/**
* Return the item's name
*/
getItemName(): Observable<string> {
return this.item$.pipe(
map((item: Item) => this.nameService.getName(item))
);
}

/**
* Return all item's bundles
*
* @return an observable that emits all item's bundles
*/
getItemBundles(): Observable<Bundle[]> {
return this.bundles$.asObservable();
}

/**
* Get all bundles per item
* and all the bitstreams per bundle
* @param page number of current page
*/
getBundlesPerItem(page: number = 1) {
this.item$ = this.route.data.pipe(
map((data) => data.dso),
getFirstSucceededRemoteDataWithNotEmptyPayload(),
map((item: Item) => this.linkService.resolveLink(
item,
followLink('bundles', {}, followLink('bitstreams'))
followLink('bundles', {findListOptions: {currentPage : page, elementsPerPage: this.bundlesPerPage}}, followLink('bitstreams'))
))
) as Observable<Item>;

const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
const bundles$: Observable<PaginatedList<Bundle>> = this.item$.pipe(
filter((item: Item) => isNotEmpty(item.bundles)),
mergeMap((item: Item) => item.bundles),
getFirstSucceededRemoteDataWithNotEmptyPayload(),
Expand All @@ -96,37 +170,36 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
take(1),
map((list: PaginatedList<Bundle>) => list.page)
).subscribe((bundles: Bundle[]) => {
this.bundles$.next(bundles);
if (isEqual(bundles.length,0) || bundles.length < this.bundlesPerPage) {
this.allBundlesLoaded = true;
}
if (isEqual(page, 1)) {
this.bundles$.next(bundles);
} else {
this.bundles$.next(this.bundles$.getValue().concat(bundles));
}
}),
bundles$.pipe(
take(1),
mergeMap((list: PaginatedList<Bundle>) => list.page),
map((bundle: Bundle) => ({ id: bundle.id, bitstreams: this.getBundleBitstreams(bundle) }))
).subscribe((entry: BundleBitstreamsMapEntry) => {
this.bundleBitstreamsMap.set(entry.id, entry.bitstreams);
let bitstreamMapValues: BitstreamMapValue = {
isCollapsed: true,
allBitstreamsLoaded: false,
bitstreams: null
};
bitstreamMapValues.bitstreams = entry.bitstreams.pipe(
map((b: PaginatedList<Bitstream>) => {
bitstreamMapValues.allBitstreamsLoaded = b?.page.length < this.bitstreamSize;
return [...b.page.slice(0, this.bitstreamSize)];
})
);
this.bundleBitstreamsMap.set(entry.id, bitstreamMapValues);
})
);
}

/**
* Return the item's UUID
*/
getItemUUID(): Observable<string> {
return this.item$.pipe(
map((item: Item) => item.id),
first((UUID: string) => isNotEmpty(UUID))
);
}

/**
* Return all item's bundles
*
* @return an observable that emits all item's bundles
*/
getItemBundles(): Observable<Bundle[]> {
return this.bundles$.asObservable();
}

/**
* Return all bundle's bitstreams
*
Expand All @@ -142,6 +215,46 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
);
}

/**
* Changes the collapsible state of the area that contains the bitstream list
* @param bundleId Id of bundle responsible for the requested bitstreams
*/
collapseArea(bundleId: string) {
this.bundleBitstreamsMap.get(bundleId).isCollapsed = !this.bundleBitstreamsMap.get(bundleId).isCollapsed;
}

/**
* Loads as much bundles as initial value of bundleSize to be displayed
*/
onBundleLoad(){
this.bundlesPageSize ++;
this.getBundlesPerItem(this.bundlesPageSize);
}

/**
* Calculates the bitstreams that are going to be loaded on demand,
* based on the number configured on this.bitstreamSize.
* @param bundle parent of bitstreams that are requested to be shown
* @returns Subscription
*/
onBitstreamsLoad(bundle: Bundle) {
return this.getBundleBitstreams(bundle).subscribe((res: PaginatedList<Bitstream>) => {
let nextBitstreams = res?.page.slice(this.bitstreamPageSize, this.bitstreamPageSize + this.bitstreamSize);
let bitstreamsToShow = this.bundleBitstreamsMap.get(bundle.id).bitstreams.pipe(
map((existingBits: Bitstream[])=> {
return [... existingBits, ...nextBitstreams];
})
);
this.bitstreamPageSize = this.bitstreamPageSize + this.bitstreamSize;
let bitstreamMapValues: BitstreamMapValue = {
bitstreams: bitstreamsToShow ,
isCollapsed: this.bundleBitstreamsMap.get(bundle.id).isCollapsed,
allBitstreamsLoaded: res?.page.length <= this.bitstreamPageSize
};
this.bundleBitstreamsMap.set(bundle.id, bitstreamMapValues);
});
}

/**
* Unsubscribe from all subscriptions
*/
Expand All @@ -151,3 +264,9 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
.forEach((subscription) => subscription.unsubscribe());
}
}

export interface BitstreamMapValue {
bitstreams: Observable<Bitstream[]>;
isCollapsed: boolean;
allBitstreamsLoaded: boolean;
}
Loading