62589: Review 03-10-2019 Changes and fixes

This commit is contained in:
Kristof De Langhe
2019-10-08 13:06:31 +02:00
parent 8652c4bce8
commit 5b776b605a
17 changed files with 118 additions and 117 deletions

View File

@@ -128,8 +128,28 @@
"collection.delete.notification.fail": "Collection could not be deleted", "collection.delete.notification.fail": "Collection could not be deleted",
"collection.delete.notification.success": "Successfully deleted collection", "collection.delete.notification.success": "Successfully deleted collection",
"collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"", "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"",
"collection.edit.delete": "Delete this collection", "collection.edit.delete": "Delete this collection",
"collection.edit.head": "Edit Collection", "collection.edit.head": "Edit Collection",
"collection.edit.item-mapper.cancel": "Cancel",
"collection.edit.item-mapper.collection": "Collection: \"<b>{{name}}</b>\"",
"collection.edit.item-mapper.confirm": "Map selected items",
"collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
"collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections",
"collection.edit.item-mapper.no-search": "Please enter a query to search",
"collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.",
"collection.edit.item-mapper.notifications.map.error.head": "Mapping errors",
"collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.",
"collection.edit.item-mapper.notifications.map.success.head": "Mapping completed",
"collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.",
"collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors",
"collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.",
"collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed",
"collection.edit.item-mapper.remove": "Remove selected item mappings",
"collection.edit.item-mapper.tabs.browse": "Browse mapped items",
"collection.edit.item-mapper.tabs.map": "Map new items",
"collection.form.abstract": "Short Description", "collection.form.abstract": "Short Description",
"collection.form.description": "Introductory text (HTML)", "collection.form.description": "Introductory text (HTML)",
"collection.form.errors.title.required": "Please enter a collection name", "collection.form.errors.title.required": "Please enter a collection name",
@@ -139,29 +159,13 @@
"collection.form.tableofcontents": "News (HTML)", "collection.form.tableofcontents": "News (HTML)",
"collection.form.title": "Name", "collection.form.title": "Name",
"collection.item-mapper.cancel": "Cancel",
"collection.item-mapper.collection": "Collection: \"<b>{{name}}</b>\"",
"collection.item-mapper.confirm": "Map selected items",
"collection.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
"collection.item-mapper.head": "Item Mapper - Map Items from Other Collections",
"collection.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.",
"collection.item-mapper.notifications.map.error.head": "Mapping errors",
"collection.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.",
"collection.item-mapper.notifications.map.success.head": "Mapping completed",
"collection.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.",
"collection.item-mapper.notifications.unmap.error.head": "Remove mapping errors",
"collection.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.",
"collection.item-mapper.notifications.unmap.success.head": "Remove mapping completed",
"collection.item-mapper.remove": "Remove selected item mappings",
"collection.item-mapper.tabs.browse": "Browse",
"collection.item-mapper.tabs.map": "Map",
"collection.page.browse.recent.head": "Recent Submissions", "collection.page.browse.recent.head": "Recent Submissions",
"collection.page.browse.recent.empty": "No items to show", "collection.page.browse.recent.empty": "No items to show",
"collection.page.license": "License", "collection.page.license": "License",
"collection.page.news": "News", "collection.page.news": "News",
"collection.select.confirm": "Confirm selected", "collection.select.confirm": "Confirm selected",
"collection.select.empty": "No collections to show",
"collection.select.table.title": "Title", "collection.select.table.title": "Title",
"community.create.head": "Create a Community", "community.create.head": "Create a Community",
@@ -258,6 +262,7 @@
"item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.", "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.",
"item.edit.item-mapper.head": "Item Mapper - Map Item to Collections", "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections",
"item.edit.item-mapper.item": "Item: \"<b>{{name}}</b>\"", "item.edit.item-mapper.item": "Item: \"<b>{{name}}</b>\"",
"item.edit.item-mapper.no-search": "Please enter a query to search",
"item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.",
"item.edit.item-mapper.notifications.add.error.head": "Mapping errors", "item.edit.item-mapper.notifications.add.error.head": "Mapping errors",
"item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.", "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.",
@@ -266,8 +271,8 @@
"item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors", "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors",
"item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.", "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.",
"item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed", "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed",
"item.edit.item-mapper.tabs.browse": "Browse", "item.edit.item-mapper.tabs.browse": "Browse mapped collections",
"item.edit.item-mapper.tabs.map": "Map", "item.edit.item-mapper.tabs.map": "Map new collections",
"item.edit.metadata.add-button": "Add", "item.edit.metadata.add-button": "Add",
"item.edit.metadata.discard-button": "Discard", "item.edit.metadata.discard-button": "Discard",
@@ -401,6 +406,7 @@
"item.page.uri": "URI", "item.page.uri": "URI",
"item.select.confirm": "Confirm selected", "item.select.confirm": "Confirm selected",
"item.select.empty": "No items to show",
"item.select.table.author": "Author", "item.select.table.author": "Author",
"item.select.table.collection": "Collection", "item.select.table.collection": "Collection",
"item.select.table.title": "Title", "item.select.table.title": "Title",

View File

@@ -1,20 +1,20 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<h2>{{'collection.item-mapper.head' | translate}}</h2> <h2>{{'collection.edit.item-mapper.head' | translate}}</h2>
<p [innerHTML]="'collection.item-mapper.collection' | translate:{ name: (collectionRD$ | async)?.payload?.name }" id="collection-name"></p> <p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionRD$ | async)?.payload?.name }" id="collection-name"></p>
<p>{{'collection.item-mapper.description' | translate}}</p> <p>{{'collection.edit.item-mapper.description' | translate}}</p>
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset"> <ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
<ngb-tab title="{{'collection.item-mapper.tabs.browse' | translate}}" id="browseTab"> <ngb-tab title="{{'collection.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
<ng-template ngbTabContent> <ng-template ngbTabContent>
<div class="mt-2"> <div class="mt-2">
<ds-item-select class="mt-2" <ds-item-select class="mt-2"
[key]="'browse'" [key]="'browse'"
[dsoRD$]="collectionItemsRD$" [dsoRD$]="collectionItemsRD$"
[paginationOptions]="(searchOptions$ | async)?.pagination" [paginationOptions]="(searchOptions$ | async)?.pagination"
[confirmButton]="'collection.item-mapper.remove'" [confirmButton]="'collection.edit.item-mapper.remove'"
[cancelButton]="'collection.item-mapper.cancel'" [cancelButton]="'collection.edit.item-mapper.cancel'"
[dangerConfirm]="true" [dangerConfirm]="true"
[hideCollection]="true" [hideCollection]="true"
(confirm)="mapItems($event, true)" (confirm)="mapItems($event, true)"
@@ -22,7 +22,7 @@
</div> </div>
</ng-template> </ng-template>
</ngb-tab> </ngb-tab>
<ngb-tab title="{{'collection.item-mapper.tabs.map' | translate}}" id="mapTab"> <ngb-tab title="{{'collection.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
<ng-template ngbTabContent> <ng-template ngbTabContent>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-12 col-lg-6"> <div class="col-12 col-lg-6">
@@ -30,21 +30,25 @@
[query]="(searchOptions$ | async)?.query" [query]="(searchOptions$ | async)?.query"
[scope]="(searchOptions$ | async)?.scope" [scope]="(searchOptions$ | async)?.scope"
[currentUrl]="'./'" [currentUrl]="'./'"
[inPlaceSearch]="true"> [inPlaceSearch]="true"
(submitSearch)="performedSearch = true">
</ds-search-form> </ds-search-form>
</div> </div>
</div> </div>
<div> <div *ngIf="performedSearch">
<ds-item-select class="mt-2" <ds-item-select class="mt-2"
[key]="'map'" [key]="'map'"
[dsoRD$]="mappedItemsRD$" [dsoRD$]="mappedItemsRD$"
[paginationOptions]="(searchOptions$ | async)?.pagination" [paginationOptions]="(searchOptions$ | async)?.pagination"
[confirmButton]="'collection.item-mapper.confirm'" [confirmButton]="'collection.edit.item-mapper.confirm'"
[cancelButton]="'collection.item-mapper.cancel'" [cancelButton]="'collection.edit.item-mapper.cancel'"
(confirm)="mapItems($event)" (confirm)="mapItems($event)"
(cancel)="onCancel()"></ds-item-select> (cancel)="onCancel()"></ds-item-select>
</div> </div>
<div *ngIf="!performedSearch" class="alert alert-info w-100" role="alert">
{{'collection.edit.item-mapper.no-search' | translate}}
</div>
</ng-template> </ng-template>
</ngb-tab> </ngb-tab>
</ngb-tabset> </ngb-tabset>

View File

@@ -84,6 +84,12 @@ export class CollectionItemMapperComponent implements OnInit {
*/ */
shouldUpdate$: BehaviorSubject<boolean>; shouldUpdate$: BehaviorSubject<boolean>;
/**
* Track whether at least one search has been performed or not
* As soon as at least one search has been performed, we display the search results
*/
performedSearch = false;
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private router: Router, private router: Router,
@Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService, @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService,
@@ -128,7 +134,7 @@ export class CollectionItemMapperComponent implements OnInit {
scope: undefined, scope: undefined,
dsoType: DSpaceObjectType.ITEM, dsoType: DSpaceObjectType.ITEM,
sort: this.defaultSortOptions sort: this.defaultSortOptions
})).pipe( }), 1000).pipe(
toDSpaceObjectListRD(), toDSpaceObjectListRD(),
startWith(undefined) startWith(undefined)
); );
@@ -154,7 +160,6 @@ export class CollectionItemMapperComponent implements OnInit {
); );
this.showNotifications(responses$, remove); this.showNotifications(responses$, remove);
this.clearRequestCache();
} }
/** /**
@@ -170,8 +175,8 @@ export class CollectionItemMapperComponent implements OnInit {
const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful); const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
if (successful.length > 0) { if (successful.length > 0) {
const successMessages = observableCombineLatest( const successMessages = observableCombineLatest(
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.head`), this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`),
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length }) this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length })
); );
successMessages.subscribe(([head, content]) => { successMessages.subscribe(([head, content]) => {
@@ -180,8 +185,8 @@ export class CollectionItemMapperComponent implements OnInit {
} }
if (unsuccessful.length > 0) { if (unsuccessful.length > 0) {
const unsuccessMessages = observableCombineLatest( const unsuccessMessages = observableCombineLatest(
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.head`), this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.head`),
this.translateService.get(`collection.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length }) this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length })
); );
unsuccessMessages.subscribe(([head, content]) => { unsuccessMessages.subscribe(([head, content]) => {
@@ -194,21 +199,12 @@ export class CollectionItemMapperComponent implements OnInit {
}); });
} }
/**
* Clear all previous requests from cache in preparation of refreshing all lists
*/
private clearRequestCache() {
this.collectionRD$.pipe(take(1)).subscribe((collectionRD: RemoteData<Collection>) => {
this.collectionDataService.clearMappedItemsRequests(collectionRD.payload.id);
this.searchService.clearDiscoveryRequests();
});
}
/** /**
* Clear url parameters on tab change (temporary fix until pagination is improved) * Clear url parameters on tab change (temporary fix until pagination is improved)
* @param event * @param event
*/ */
tabChange(event) { tabChange(event) {
this.performedSearch = false;
this.router.navigateByUrl(this.getCurrentUrl()); this.router.navigateByUrl(this.getCurrentUrl());
} }

View File

@@ -64,7 +64,7 @@ const COLLECTION_EDIT_PATH = ':id/edit';
} }
}, },
{ {
path: ':id/mapper', path: ':id/edit/mapper',
component: CollectionItemMapperComponent, component: CollectionItemMapperComponent,
pathMatch: 'full', pathMatch: 'full',
resolve: { resolve: {

View File

@@ -28,12 +28,13 @@
<ds-search-form id="search-form" <ds-search-form id="search-form"
[query]="(searchOptions$ | async)?.query" [query]="(searchOptions$ | async)?.query"
[currentUrl]="'./'" [currentUrl]="'./'"
[inPlaceSearch]="true"> [inPlaceSearch]="true"
(submitSearch)="performedSearch = true">
</ds-search-form> </ds-search-form>
</div> </div>
</div> </div>
<div> <div *ngIf="performedSearch">
<ds-collection-select class="mt-2" <ds-collection-select class="mt-2"
[key]="'map'" [key]="'map'"
[dsoRD$]="mappedCollectionsRD$" [dsoRD$]="mappedCollectionsRD$"
@@ -44,6 +45,9 @@
(confirm)="mapCollections($event)" (confirm)="mapCollections($event)"
(cancel)="onCancel()"></ds-collection-select> (cancel)="onCancel()"></ds-collection-select>
</div> </div>
<div *ngIf="!performedSearch" class="alert alert-info w-100" role="alert">
{{'item.edit.item-mapper.no-search' | translate}}
</div>
</ng-template> </ng-template>
</ngb-tab> </ngb-tab>
</ngb-tabset> </ngb-tabset>

View File

@@ -69,6 +69,12 @@ export class ItemCollectionMapperComponent implements OnInit {
*/ */
shouldUpdate$: BehaviorSubject<boolean>; shouldUpdate$: BehaviorSubject<boolean>;
/**
* Track whether at least one search has been performed or not
* As soon as at least one search has been performed, we display the search results
*/
performedSearch = false;
constructor(private route: ActivatedRoute, constructor(private route: ActivatedRoute,
private router: Router, private router: Router,
private searchConfigService: SearchConfigurationService, private searchConfigService: SearchConfigurationService,
@@ -112,7 +118,7 @@ export class ItemCollectionMapperComponent implements OnInit {
return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), { return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), {
query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query), query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query),
dsoType: DSpaceObjectType.COLLECTION dsoType: DSpaceObjectType.COLLECTION
})).pipe( }), 1000).pipe(
toDSpaceObjectListRD(), toDSpaceObjectListRD(),
startWith(undefined) startWith(undefined)
); );
@@ -146,7 +152,6 @@ export class ItemCollectionMapperComponent implements OnInit {
); );
this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add'); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add');
this.clearRequestCache();
} }
/** /**
@@ -161,7 +166,6 @@ export class ItemCollectionMapperComponent implements OnInit {
); );
this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove'); this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove');
this.clearRequestCache();
} }
/** /**
@@ -209,21 +213,12 @@ export class ItemCollectionMapperComponent implements OnInit {
}); });
} }
/**
* Clear all previous requests from cache in preparation of refreshing all lists
*/
private clearRequestCache() {
this.itemRD$.pipe(take(1)).subscribe((itemRD: RemoteData<Item>) => {
this.itemDataService.clearMappedCollectionsRequests(itemRD.payload.id);
this.searchService.clearDiscoveryRequests();
});
}
/** /**
* Clear url parameters on tab change (temporary fix until pagination is improved) * Clear url parameters on tab change (temporary fix until pagination is improved)
* @param event * @param event
*/ */
tabChange(event) { tabChange(event) {
this.performedSearch = false;
this.router.navigateByUrl(this.getCurrentUrl()); this.router.navigateByUrl(this.getCurrentUrl());
} }

View File

@@ -100,9 +100,10 @@ export class SearchService implements OnDestroy {
/** /**
* Method to retrieve a paginated list of search results from the server * Method to retrieve a paginated list of search results from the server
* @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search * @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search
* @param responseMsToLive The amount of milliseconds for the response to live in cache
* @returns {Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>} Emits a paginated list with all search results found * @returns {Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>} Emits a paginated list with all search results found
*/ */
search(searchOptions?: PaginatedSearchOptions): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> { search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe( const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe(
map((url: string) => { map((url: string) => {
if (hasValue(searchOptions)) { if (hasValue(searchOptions)) {
@@ -122,6 +123,7 @@ export class SearchService implements OnDestroy {
}; };
return Object.assign(request, { return Object.assign(request, {
responseMsToLive: hasValue(responseMsToLive) ? responseMsToLive : request.responseMsToLive,
getResponseParser: getResponseParserFn getResponseParser: getResponseParserFn
}); });
}), }),
@@ -373,15 +375,6 @@ export class SearchService implements OnDestroy {
return '/search'; return '/search';
} }
/**
* Clear all request cache related to discovery objects
*/
clearDiscoveryRequests() {
this.halService.getEndpoint(this.searchLinkPath).pipe(take(1)).subscribe((href: string) => {
this.requestService.removeByHrefSubstring(href);
});
}
/** /**
* Unsubscribe from the subscription * Unsubscribe from the subscription
*/ */

View File

@@ -41,14 +41,4 @@ describe('CollectionDataService', () => {
}); });
}); });
describe('clearMappedItemsRequests', () => {
beforeEach(() => {
service.clearMappedItemsRequests('collection-id');
});
it('should remote request cache', () => {
expect(requestService.removeByHrefSubstring).toHaveBeenCalled();
});
});
}); });

View File

@@ -125,6 +125,7 @@ export class CollectionDataService extends ComColDataService<Collection> {
map((endpoint: string) => { map((endpoint: string) => {
const request = new GetRequest(requestUuid, endpoint); const request = new GetRequest(requestUuid, endpoint);
return Object.assign(request, { return Object.assign(request, {
responseMsToLive: 0,
getResponseParser(): GenericConstructor<ResponseParsingService> { getResponseParser(): GenericConstructor<ResponseParsingService> {
return DSOResponseParsingService; return DSOResponseParsingService;
} }
@@ -136,14 +137,4 @@ export class CollectionDataService extends ComColDataService<Collection> {
return this.rdbService.buildList(href$); return this.rdbService.buildList(href$);
} }
/**
* Clears all requests (from cache) connected to the mappedItems endpoint
* @param collectionId
*/
clearMappedItemsRequests(collectionId: string) {
this.getMappedItemsEndpoint(collectionId).pipe(take(1)).subscribe((href: string) => {
this.requestService.removeByHrefSubstring(href);
});
}
} }

View File

@@ -148,16 +148,6 @@ export class ItemDataService extends DataService<Item> {
return this.rdbService.toRemoteDataObservable(requestEntry$, payload$); return this.rdbService.toRemoteDataObservable(requestEntry$, payload$);
} }
/**
* Clears all requests (from cache) connected to the mappedCollections endpoint
* @param itemId
*/
public clearMappedCollectionsRequests(itemId: string) {
this.getMappedCollectionsEndpoint(itemId).pipe(take(1)).subscribe((href: string) => {
this.requestService.removeByHrefSubstring(href);
});
}
/** /**
* Get the endpoint for item withdrawal and reinstatement * Get the endpoint for item withdrawal and reinstatement
* @param itemId * @param itemId

View File

@@ -190,6 +190,8 @@ export class BrowseItemsRequest extends GetRequest {
* Request to fetch the mapped collections of an item * Request to fetch the mapped collections of an item
*/ */
export class MappedCollectionsRequest extends GetRequest { export class MappedCollectionsRequest extends GetRequest {
public responseMsToLive = 0;
getResponseParser(): GenericConstructor<ResponseParsingService> { getResponseParser(): GenericConstructor<ResponseParsingService> {
return MappedCollectionsReponseParsingService; return MappedCollectionsReponseParsingService;
} }

View File

@@ -7,7 +7,7 @@
[collectionSize]="collectionsRD?.payload?.totalElements" [collectionSize]="collectionsRD?.payload?.totalElements"
[hidePagerWhenSinglePage]="true" [hidePagerWhenSinglePage]="true"
[hideGear]="true"> [hideGear]="true">
<div class="table-responsive"> <div class="table-responsive mt-2">
<table id="collection-select" class="table table-striped table-hover"> <table id="collection-select" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@@ -24,11 +24,18 @@
</table> </table>
</div> </div>
</ds-pagination> </ds-pagination>
<div *ngIf="collectionsRD?.payload?.totalElements === 0 || collectionsRD?.payload?.page?.length === 0" class="alert alert-info w-100" role="alert">
{{'collection.select.empty' | translate}}
</div>
<ds-error *ngIf="collectionsRD?.hasFailed" message="{{'error.collections' | translate}}"></ds-error> <ds-error *ngIf="collectionsRD?.hasFailed" message="{{'error.collections' | translate}}"></ds-error>
<ds-loading *ngIf="!collectionsRD || collectionsRD?.isLoading" message="{{'loading.collections' | translate}}"></ds-loading> <ds-loading *ngIf="!collectionsRD || collectionsRD?.isLoading" message="{{'loading.collections' | translate}}"></ds-loading>
<div> <div *ngVar="(selectedIds$ | async) as selectedIds">
<button class="btn btn-outline-secondary collection-cancel float-left" (click)="onCancel()">{{cancelButton | translate}}</button> <button class="btn btn-outline-secondary collection-cancel float-left" (click)="onCancel()">{{cancelButton | translate}}</button>
<button *ngIf="dangerConfirm" class="btn btn-danger collection-confirm float-right" (click)="confirmSelected()">{{confirmButton | translate}}</button> <button class="btn collection-confirm float-right"
<button *ngIf="!dangerConfirm" class="btn btn-primary collection-confirm float-right" (click)="confirmSelected()">{{confirmButton | translate}}</button> [ngClass]="{'btn-danger': dangerConfirm, 'btn-primary': !dangerConfirm}"
[disabled]="selectedIds?.length === 0"
(click)="confirmSelected()">
{{confirmButton | translate}}
</button>
</div> </div>
</ng-container> </ng-container>

View File

@@ -16,7 +16,7 @@ import { CollectionSelectComponent } from './collection-select.component';
import { Collection } from '../../../core/shared/collection.model'; import { Collection } from '../../../core/shared/collection.model';
import { of } from 'rxjs/internal/observable/of'; import { of } from 'rxjs/internal/observable/of';
describe('ItemSelectComponent', () => { describe('CollectionSelectComponent', () => {
let comp: CollectionSelectComponent; let comp: CollectionSelectComponent;
let fixture: ComponentFixture<CollectionSelectComponent>; let fixture: ComponentFixture<CollectionSelectComponent>;
let objectSelectService: ObjectSelectService; let objectSelectService: ObjectSelectService;
@@ -43,7 +43,7 @@ describe('ItemSelectComponent', () => {
imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])],
declarations: [], declarations: [],
providers: [ providers: [
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockCollectionList[1].id]) },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) } { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -7,7 +7,7 @@
[collectionSize]="itemsRD?.payload?.totalElements" [collectionSize]="itemsRD?.payload?.totalElements"
[hidePagerWhenSinglePage]="true" [hidePagerWhenSinglePage]="true"
[hideGear]="true"> [hideGear]="true">
<div class="table-responsive"> <div class="table-responsive mt-2">
<table id="item-select" class="table table-striped table-hover"> <table id="item-select" class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
@@ -20,19 +20,30 @@
<tbody> <tbody>
<tr *ngFor="let item of itemsRD?.payload?.page"> <tr *ngFor="let item of itemsRD?.payload?.page">
<td><input class="item-checkbox" [ngModel]="getSelected(item.id) | async" (change)="switch(item.id)" type="checkbox" name="{{item.id}}"></td> <td><input class="item-checkbox" [ngModel]="getSelected(item.id) | async" (change)="switch(item.id)" type="checkbox" name="{{item.id}}"></td>
<td *ngIf="!hideCollection"><a [routerLink]="['/items', item.id]">{{(item.owningCollection | async)?.payload?.name}}</a></td> <td *ngIf="!hideCollection">
<td><a *ngIf="item.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])" [routerLink]="['/items', item.id]">{{item.firstMetadataValue(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])}}</a></td> <span *ngVar="(item.owningCollection | async)?.payload as collection">
<a *ngIf="collection" [routerLink]="['/collections', collection?.id]">{{collection?.name}}</a>
</span>
</td>
<td><span *ngIf="item.hasMetadata(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])">{{item.firstMetadataValue(['dc.contributor.author', 'dc.creator', 'dc.contributor.*'])}}</span></td>
<td><a [routerLink]="['/items', item.id]">{{item.firstMetadataValue("dc.title")}}</a></td> <td><a [routerLink]="['/items', item.id]">{{item.firstMetadataValue("dc.title")}}</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</ds-pagination> </ds-pagination>
<div *ngIf="itemsRD?.payload?.totalElements === 0 || itemsRD?.payload?.page?.length === 0" class="alert alert-info w-100" role="alert">
{{'item.select.empty' | translate}}
</div>
<ds-error *ngIf="itemsRD?.hasFailed" message="{{'error.items' | translate}}"></ds-error> <ds-error *ngIf="itemsRD?.hasFailed" message="{{'error.items' | translate}}"></ds-error>
<ds-loading *ngIf="!itemsRD || itemsRD?.isLoading" message="{{'loading.items' | translate}}"></ds-loading> <ds-loading *ngIf="!itemsRD || itemsRD?.isLoading" message="{{'loading.items' | translate}}"></ds-loading>
<div> <div *ngVar="(selectedIds$ | async) as selectedIds">
<button class="btn btn-outline-secondary item-cancel float-left" (click)="onCancel()">{{cancelButton | translate}}</button> <button class="btn btn-outline-secondary item-cancel float-left" (click)="onCancel()">{{cancelButton | translate}}</button>
<button *ngIf="dangerConfirm" class="btn btn-danger item-confirm float-right" (click)="confirmSelected()">{{confirmButton | translate}}</button> <button class="btn item-confirm float-right"
<button *ngIf="!dangerConfirm" class="btn btn-primary item-confirm float-right" (click)="confirmSelected()">{{confirmButton | translate}}</button> [ngClass]="{'btn-danger': dangerConfirm, 'btn-primary': !dangerConfirm}"
[disabled]="selectedIds?.length === 0"
(click)="confirmSelected()">
{{confirmButton | translate}}
</button>
</div> </div>
</ng-container> </ng-container>

View File

@@ -65,7 +65,7 @@ describe('ItemSelectComponent', () => {
imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])], imports: [TranslateModule.forRoot(), SharedModule, RouterTestingModule.withRoutes([])],
declarations: [], declarations: [],
providers: [ providers: [
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() }, { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub([mockItemList[1].id]) },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) } { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -51,7 +51,13 @@ export class ObjectSelectService {
*/ */
getAllSelected(key: string): Observable<string[]> { getAllSelected(key: string): Observable<string[]> {
return this.appStore.select(objectSelectionListStateSelector).pipe( return this.appStore.select(objectSelectionListStateSelector).pipe(
map((state: ObjectSelectionListState) => Object.keys(state[key]).filter((id) => state[key][id].checked)) map((state: ObjectSelectionListState) => {
if (hasValue(state[key])) {
return Object.keys(state[key]).filter((id) => state[key][id].checked);
} else {
return [];
}
})
); );
} }

View File

@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { hasValue, isNotEmpty } from '../empty.util'; import { hasValue, isNotEmpty } from '../empty.util';
@@ -56,6 +56,11 @@ export class SearchFormComponent {
*/ */
@Input() brandColor = 'primary'; @Input() brandColor = 'primary';
/**
* Output the search data on submit
*/
@Output() submitSearch = new EventEmitter<any>();
constructor(private router: Router, private searchService: SearchService) { constructor(private router: Router, private searchService: SearchService) {
} }
@@ -65,6 +70,7 @@ export class SearchFormComponent {
*/ */
onSubmit(data: any) { onSubmit(data: any) {
this.updateSearch(data); this.updateSearch(data);
this.submitSearch.emit(data);
} }
/** /**