finished second selection tab

This commit is contained in:
lotte
2019-09-04 15:45:08 +02:00
parent 77e2fde15a
commit 11ad52c120
19 changed files with 278 additions and 172 deletions

View File

@@ -524,6 +524,9 @@
"search.filters.applied.f.namedresourcetype": "Status", "search.filters.applied.f.namedresourcetype": "Status",
"search.filters.applied.f.subject": "Subject", "search.filters.applied.f.subject": "Subject",
"search.filters.applied.f.submitter": "Submitter", "search.filters.applied.f.submitter": "Submitter",
"search.filters.applied.f.jobTitle": "Job Title",
"search.filters.applied.f.birthDate.max": "End birth date",
"search.filters.applied.f.birthDate.min": "Start birth date",
"search.filters.filter.author.head": "Author", "search.filters.filter.author.head": "Author",
"search.filters.filter.author.placeholder": "Author name", "search.filters.filter.author.placeholder": "Author name",
"search.filters.filter.birthDate.head": "Birth Date", "search.filters.filter.birthDate.head": "Birth Date",
@@ -603,19 +606,30 @@
"submission.general.save-later": "Save for later", "submission.general.save-later": "Save for later",
"submission.mydspace": {}, "submission.mydspace": {},
"submission.sections.describe.relationship-lookup.close": "Close", "submission.sections.describe.relationship-lookup.close": "Close",
"submission.sections.describe.relationship-lookup.deselect-all": "Deselect all", "submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
"submission.sections.describe.relationship-lookup.deselect-page": "Deselect page", "submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
"submission.sections.describe.relationship-lookup.loading": "Loading...", "submission.sections.describe.relationship-lookup.search-tab.loading": "Loading...",
"submission.sections.describe.relationship-lookup.placeholder": "Search query", "submission.sections.describe.relationship-lookup.search-tab.placeholder": "Search query",
"submission.sections.describe.relationship-lookup.search": "Go", "submission.sections.describe.relationship-lookup.search-tab.search": "Go",
"submission.sections.describe.relationship-lookup.select-all": "Select all", "submission.sections.describe.relationship-lookup.search-tab.select-all": "Select all",
"submission.sections.describe.relationship-lookup.select-page": "Select page", "submission.sections.describe.relationship-lookup.search-tab.select-page": "Select page",
"submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items", "submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items",
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Select a Journal Issue", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Author": "Search for Authors",
"submission.sections.describe.relationship-lookup.title.Journal Volume": "Select a Journal Volume", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Search for Journals",
"submission.sections.describe.relationship-lookup.title.Journal": "Select a Journal", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Issue": "Search for Journal Issues",
"submission.sections.describe.relationship-lookup.title.Author": "Select an Author", "submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
"submission.sections.describe.relationship-lookup.toggle-dropdown": "Toggle dropdown", "submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})",
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
"submission.sections.describe.relationship-lookup.title.Journal Volume": "Journal Volumes",
"submission.sections.describe.relationship-lookup.title.Journal": "Journals",
"submission.sections.describe.relationship-lookup.title.Author": "Authors",
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
"submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Your selection is currently empty.",
"submission.sections.describe.relationship-lookup.selection-tab.title.Author": "Selected Authors",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Volume": "Selected Journal Volume",
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Issue": "Selected Issue",
"submission.sections.general.add-more": "Add more", "submission.sections.general.add-more": "Add more",
"submission.sections.general.collection": "Collection", "submission.sections.general.collection": "Collection",
"submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.", "submission.sections.general.deposit_error_notice": "There was an issue when submitting the item, please try again later.",

View File

@@ -11,7 +11,11 @@
[scopes]="(scopeListRD$ | async)" [scopes]="(scopeListRD$ | async)"
[inPlaceSearch]="inPlaceSearch"> [inPlaceSearch]="inPlaceSearch">
</ds-search-form> </ds-search-form>
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels> <div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3">
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
</div>
</div>
<div class="row"> <div class="row">
<div id="search-body" <div id="search-body"
class="row-offcanvas row-offcanvas-left" class="row-offcanvas row-offcanvas-left"
@@ -28,7 +32,7 @@
<button (click)="openSidebar()" aria-controls="#search-body" <button (click)="openSidebar()" aria-controls="#search-body"
class="btn btn-outline-primary float-right open-sidebar"><i class="btn btn-outline-primary float-right open-sidebar"><i
class="fas fa-sliders"></i> {{"search.sidebar.open" class="fas fa-sliders"></i> {{"search.sidebar.open"
| translate}} | translate}}
</button> </button>
</div> </div>
<ds-search-results [searchResults]="resultsRD$ | async" <ds-search-results [searchResults]="resultsRD$ | async"

View File

@@ -1,9 +1,26 @@
import { autoserialize, autoserializeAs } from 'cerialize'; import { autoserialize, autoserializeAs } from 'cerialize';
import { hasValue } from '../../shared/empty.util';
/** /**
* Represents the state of a paginated response * Represents the state of a paginated response
*/ */
export class PageInfo { export class PageInfo {
constructor(
options?: {
elementsPerPage: number,
totalElements: number,
totalPages: number,
currentPage: number
}
) {
if (hasValue(options)) {
this.elementsPerPage = options.elementsPerPage;
this.totalElements = options.totalElements;
this.totalPages = options.totalPages;
this.currentPage = options.currentPage;
}
}
/** /**
* The number of elements on a page * The number of elements on a page
*/ */

View File

@@ -20,7 +20,7 @@ function equalsByFields(object1, object2, fieldList): boolean {
return !object1[key].equals(object2[key]); return !object1[key].equals(object2[key]);
} }
if (typeof object1[key] === 'object') { if (typeof object1[key] === 'object') {
equalsByFields(object1[key], object2[key], Object.keys(object1)) return !equalsByFields(object1[key], object2[key], Object.keys(object1))
} }
return object1[key] !== object2[key]; return object1[key] !== object2[key];
}); });

View File

@@ -88,6 +88,9 @@ import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model'; import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { ItemDataService } from '../../../../core/data/item-data.service'; import { ItemDataService } from '../../../../core/data/item-data.service';
import { RemoveRelationshipAction } from './relation-lookup-modal/relationship.actions';
import { Store } from '@ngrx/store';
import { AppState } from '../../../../app.reducer';
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null { export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
switch (model.type) { switch (model.type) {
@@ -202,7 +205,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
private itemService: ItemDataService, private itemService: ItemDataService,
private relationshipService: RelationshipService, private relationshipService: RelationshipService,
private zone: NgZone private zone: NgZone,
private store: Store<AppState>
) { ) {
super(componentFactoryResolver, layoutService, validationService); super(componentFactoryResolver, layoutService, validationService);
} }
@@ -279,11 +283,10 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
removeSelection(object: SearchResult<Item>) { removeSelection(object: SearchResult<Item>) {
this.selectableListService.deselectSingle(this.listId, object); this.selectableListService.deselectSingle(this.listId, object);
this.zone.runOutsideAngular( this.zone.runOutsideAngular(
this.model.workspaceItem.item.pipe( () => this.model.workspaceItem.item.pipe(
getSucceededRemoteData(), getSucceededRemoteData(),
switchMap((itemRD: RemoteData<Item>) => this.relationshipService.getRelationshipByItemsAndLabel(itemRD.payload, object.indexableObject, this.model.relationship.relationshipType)), tap((itemRD: RemoteData<Item>) => this.store.dispatch(new RemoveRelationshipAction(itemRD.payload, object.indexableObject, this.model.relationship.relationshipType)))
switchMap((relationship: Relationship) => this.relationshipService.deleteRelationship(relationship.id)), ).subscribe()
take(1) );
).subscribe());
} }
} }

View File

@@ -7,28 +7,28 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<ngb-tabset> <ngb-tabset>
<ngb-tab title="Search"> <ngb-tab [title]="'submission.sections.describe.relationship-lookup.search-tab.tab-title.' + label | translate">
<ng-template ngbTabContent> <ng-template ngbTabContent>
<ds-dynamic-lookup-relation-search-tab <ds-dynamic-lookup-relation-search-tab
[label]="label"
[itemRD$]="itemRD$"
[selection$]="selection$" [selection$]="selection$"
[listId]="listId" [listId]="listId"
[relationship]="relationship" [relationship]="relationship"
[repeatable]="repeatable" [repeatable]="repeatable"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3"> class="d-block pt-3">
</ds-dynamic-lookup-relation-search-tab> </ds-dynamic-lookup-relation-search-tab>
</ng-template> </ng-template>
</ngb-tab> </ngb-tab>
<ngb-tab title="Selection"> <ngb-tab [title]="'submission.sections.describe.relationship-lookup.selection-tab.tab-title' | translate : {count: (selection$ | async)?.length}">
<ng-template ngbTabContent> <ng-template ngbTabContent>
<ds-dynamic-lookup-relation-selection-tab <ds-dynamic-lookup-relation-selection-tab
[label]="label"
[itemRD$]="itemRD$"
[selection$]="selection$" [selection$]="selection$"
[listId]="listId" [listId]="listId"
[relationship]="relationship" [label]="label"
[repeatable]="repeatable" [repeatable]="repeatable"
(selectObject)="select($event)"
(deselectObject)="deselect($event)"
class="d-block pt-3"> class="d-block pt-3">
</ds-dynamic-lookup-relation-selection-tab> </ds-dynamic-lookup-relation-selection-tab>
</ng-template> </ng-template>

View File

@@ -1,14 +1,23 @@
import { Component, OnInit } from '@angular/core'; import { Component, NgZone, OnInit } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { hasValue } from '../../../../empty.util'; import { hasValue } from '../../../../empty.util';
import { map } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
import { SelectableListState } from '../../../../object-list/selectable-list/selectable-list.reducer'; import { SelectableListState } from '../../../../object-list/selectable-list/selectable-list.reducer';
import { ListableObject } from '../../../../object-collection/shared/listable-object.model'; import { ListableObject } from '../../../../object-collection/shared/listable-object.model';
import { RelationshipOptions } from '../../models/relationship-options.model'; import { RelationshipOptions } from '../../models/relationship-options.model';
import { SearchResult } from '../../../../search/search-result.model';
import { Item } from '../../../../../core/shared/item.model';
import { getSucceededRemoteData } from '../../../../../core/shared/operators';
import { RemoteData } from '../../../../../core/data/remote-data';
import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.actions';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
import { Store } from '@ngrx/store';
import { AppState } from '../../../../../app.reducer';
@Component({ @Component({
selector: 'ds-dynamic-lookup-relation-modal', selector: 'ds-dynamic-lookup-relation-modal',
@@ -33,6 +42,10 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
constructor( constructor(
public modal: NgbActiveModal, public modal: NgbActiveModal,
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
private relationshipService: RelationshipService,
private relationshipTypeService: RelationshipTypeService,
private zone: NgZone,
private store: Store<AppState>
) { ) {
} }
@@ -44,4 +57,32 @@ export class DsDynamicLookupRelationModalComponent implements OnInit {
close() { close() {
this.modal.close(); this.modal.close();
} }
select(...selectableObjects: SearchResult<Item>[]) {
this.zone.runOutsideAngular(
() => this.itemRD$
.pipe(
getSucceededRemoteData(),
tap((itemRD: RemoteData<Item>) => {
return selectableObjects.forEach((object) =>
this.store.dispatch(new AddRelationshipAction(itemRD.payload, object.indexableObject, this.relationship.relationshipType))
);
})
).subscribe());
}
deselect(...selectableObjects: SearchResult<Item>[]) {
this.zone.runOutsideAngular(
() => this.itemRD$.pipe(
getSucceededRemoteData(),
tap((itemRD: RemoteData<Item>) => {
return selectableObjects.forEach((object) =>
this.store.dispatch(new RemoveRelationshipAction(itemRD.payload, object.indexableObject, this.relationship.relationshipType))
);
})
).subscribe()
);
}
} }

View File

@@ -5,12 +5,15 @@
<div class="col-8"> <div class="col-8">
<form class="input-group mb-3" #queryForm="ngForm" <form class="input-group mb-3" #queryForm="ngForm"
(ngSubmit)="search(queryForm.value.query)"> (ngSubmit)="search(queryForm.value.query)">
<input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.placeholder' | translate" <input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.search-tab.placeholder' | translate"
[ngModel]="searchQuery"> [ngModel]="searchQuery">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search' | translate) }}</button> <button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search-tab.search' | translate) }}</button>
</div> </div>
</form> </form>
<ds-search-labels [inPlaceSearch]="true"></ds-search-labels>
<div *ngIf="repeatable" class="position-absolute"> <div *ngIf="repeatable" class="position-absolute">
<div class="input-group mb-3"> <div class="input-group mb-3">
<div class="input-group-prepend"> <div class="input-group-prepend">
@@ -38,7 +41,7 @@
class="btn btn-outline-secondary rounded-right"> class="btn btn-outline-secondary rounded-right">
<span class="spinner-border spinner-border-sm" role="status" <span class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span> aria-hidden="true"></span>
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.loading' | translate) }}</span> <span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.search-tab.loading' | translate) }}</span>
</button> </button>
<button id="resultdropdown" type="button" <button id="resultdropdown" type="button"
ngbDropdownToggle ngbDropdownToggle
@@ -49,10 +52,12 @@
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.toggle-dropdown' | translate) }}</span> <span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.toggle-dropdown' | translate) }}</span>
</button> </button>
<div ngbDropdownMenu aria-labelledby="resultdropdown"> <div ngbDropdownMenu aria-labelledby="resultdropdown">
<button class="dropdown-item" (click)="selectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.select-page' | translate) }}</button> <button class="dropdown-item"
<button class="dropdown-item" (click)="deselectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.deselect-page' | translate) }}</button> (click)="selectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.search-tab.select-page' | translate) }}</button>
<button class="dropdown-item" (click)="selectAll()">{{ ('submission.sections.describe.relationship-lookup.select-all' | translate) }}</button> <button class="dropdown-item"
<button class="dropdown-item" (click)="deselectAll()">{{ ('submission.sections.describe.relationship-lookup.deselect-all' | translate) }}</button> (click)="deselectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.search-tab.deselect-page' | translate) }}</button>
<button class="dropdown-item" (click)="selectAll()">{{ ('submission.sections.describe.relationship-lookup.search-tab.select-all' | translate) }}</button>
<button class="dropdown-item" (click)="deselectAll()">{{ ('submission.sections.describe.relationship-lookup.search-tab.deselect-all' | translate) }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -62,9 +67,8 @@
[searchConfig]="this.searchConfig" [searchConfig]="this.searchConfig"
[selectable]="true" [selectable]="true"
[selectionConfig]="{ repeatable: repeatable, listId: listId }" [selectionConfig]="{ repeatable: repeatable, listId: listId }"
(deselectObject)="deselect($event)" (deselectObject)="deselectObject.emit($event)"
(selectObject)="select($event)" (selectObject)="selectObject.emit($event)">
>
</ds-search-results> </ds-search-results>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
import { Component, Input, NgZone, OnDestroy, OnInit } from '@angular/core'; import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { Item } from '../../../../../../core/shared/item.model'; import { Item } from '../../../../../../core/shared/item.model';
@@ -14,16 +14,11 @@ import { SearchService } from '../../../../../../core/shared/search/search.servi
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service'; import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
import { RouteService } from '../../../../../services/route.service'; import { RouteService } from '../../../../../services/route.service';
import { RelationshipService } from '../../../../../../core/data/relationship.service';
import { RelationshipTypeService } from '../../../../../../core/data/relationship-type.service';
import { Store } from '@ngrx/store';
import { AppState } from '../../../../../../app.reducer';
import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer'; import { SelectableListState } from '../../../../../object-list/selectable-list/selectable-list.reducer';
import { hasValue, isNotEmpty } from '../../../../../empty.util'; import { hasValue, isNotEmpty } from '../../../../../empty.util';
import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators'; import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { getSucceededRemoteData } from '../../../../../../core/shared/operators'; import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
import { AddRelationshipAction, RemoveRelationshipAction } from '../relationship.actions';
@Component({ @Component({
selector: 'ds-dynamic-lookup-relation-search-tab', selector: 'ds-dynamic-lookup-relation-search-tab',
@@ -38,12 +33,12 @@ import { AddRelationshipAction, RemoveRelationshipAction } from '../relationship
}) })
export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy { export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
@Input() label: string;
@Input() relationship: RelationshipOptions; @Input() relationship: RelationshipOptions;
@Input() listId: string; @Input() listId: string;
@Input() itemRD$;
@Input() repeatable: boolean; @Input() repeatable: boolean;
@Input() selection$: Observable<ListableObject[]>; @Input() selection$: Observable<ListableObject[]>;
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>; resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
searchConfig: PaginatedSearchOptions; searchConfig: PaginatedSearchOptions;
searchQuery; searchQuery;
@@ -62,10 +57,6 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
private selectableListService: SelectableListService, private selectableListService: SelectableListService,
private searchConfigService: SearchConfigurationService, private searchConfigService: SearchConfigurationService,
private routeService: RouteService, private routeService: RouteService,
private relationshipService: RelationshipService,
private relationshipTypeService: RelationshipTypeService,
private zone: NgZone,
private store: Store<AppState>
) { ) {
} }
@@ -74,13 +65,13 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
this.routeService.setParameter('fixedFilterQuery', this.relationship.filter); this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
this.routeService.setParameter('configuration', this.relationship.searchConfiguration); this.routeService.setParameter('configuration', this.relationship.searchConfiguration);
this.selection$ = this.selectableListService.getSelectableList(this.listId).pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection))); this.someSelected$ = this.selection$.pipe(map((selection) => isNotEmpty(selection)));
this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe( this.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
map((options) => { map((options) => {
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.relationship.filter, configuration: this.relationship.searchConfiguration }) return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.relationship.filter, configuration: this.relationship.searchConfiguration })
}), }),
switchMap((options) => { switchMap((options) => {
this.searchQuery = options.query;
this.searchConfig = options; this.searchConfig = options;
return this.searchService.search(options).pipe( return this.searchService.search(options).pipe(
/* Make sure to only listen to the first x results, until loading is finished */ /* Make sure to only listen to the first x results, until loading is finished */
@@ -96,6 +87,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
) )
}) })
); );
this.resultsRD$.subscribe((t) => console.log(t));
} }
@@ -116,7 +108,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
.pipe(take(1)) .pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => { .subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0) const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0)
this.select(...filteredPage); this.selectObject.emit(...filteredPage);
}); });
this.selectableListService.select(this.listId, page); this.selectableListService.select(this.listId, page);
} }
@@ -127,7 +119,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
.pipe(take(1)) .pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => { .subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0) const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0)
this.deselect(...filteredPage); this.deselectObject.emit(...filteredPage);
}); });
this.selectableListService.deselect(this.listId, page); this.selectableListService.deselect(this.listId, page);
} }
@@ -151,7 +143,7 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
.pipe(take(1)) .pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => { .subscribe((selection: SearchResult<Item>[]) => {
const filteredResults = results.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0); const filteredResults = results.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
this.select(...filteredResults); this.selectObject.emit(...filteredResults);
}); });
this.selectableListService.select(this.listId, results); this.selectableListService.select(this.listId, results);
} }
@@ -162,38 +154,10 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
this.allSelected = false; this.allSelected = false;
this.selection$ this.selection$
.pipe(take(1)) .pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => this.deselect(...selection)); .subscribe((selection: SearchResult<Item>[]) => this.deselectObject.emit(...selection));
this.selectableListService.deselectAll(this.listId); this.selectableListService.deselectAll(this.listId);
} }
select(...selectableObjects: SearchResult<Item>[]) {
this.zone.runOutsideAngular(
() => this.itemRD$
.pipe(
getSucceededRemoteData(),
tap((itemRD: RemoteData<Item>) => {
return selectableObjects.forEach((object) =>
this.store.dispatch(new AddRelationshipAction(itemRD.payload, object.indexableObject, this.relationship.relationshipType))
);
})
).subscribe());
}
deselect(...selectableObjects: SearchResult<Item>[]) {
this.zone.runOutsideAngular(
() => this.itemRD$.pipe(
getSucceededRemoteData(),
tap((itemRD: RemoteData<Item>) => {
return selectableObjects.forEach((object) =>
this.store.dispatch(new RemoveRelationshipAction(itemRD.payload, object.indexableObject, this.relationship.relationshipType))
);
})
).subscribe()
);
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (hasValue(this.subscription) if (hasValue(this.subscription)
) { ) {

View File

@@ -1,10 +1,22 @@
<ds-search-sidebar class="col-4" id="search-sidebar" <div class="row">
[resultCount]="" <div class="col-4">
[inPlaceSearch]="true" [showViewModes]="false"></ds-search-sidebar> <h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.settings' | translate}}</h3>
<div class="col-8"> <ds-page-size-selector></ds-page-size-selector>
<ds-viewable-collection [objects]="selectionRD$ | async" </div>
[selectable]="true" <div class="col-8">
[selectionConfig]="{ repeatable: repeatable, listId: listId }" <div *ngIf="(selectionRD$ | async)?.payload.page < 1">
[config]="initialPagination" {{'submission.sections.describe.relationship-lookup.selection-tab.no-selection' | translate}}
></ds-viewable-collection> </div>
<div *ngIf="(selectionRD$ | async)?.payload.page.length >= 1">
<h3>{{ 'submission.sections.describe.relationship-lookup.selection-tab.title.' + label | translate}}</h3>
<ds-viewable-collection [objects]="selectionRD$ | async"
[selectable]="true"
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
[config]="initialPagination"
[hideGear]="true"
(deselectObject)="deselectObject.emit($event)"
(selectObject)="selectObject.emit($event)"
></ds-viewable-collection>
</div>
</div>
</div> </div>

View File

@@ -1,14 +1,20 @@
import { Component, Input } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component'; import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service'; import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { RelationshipOptions } from '../../../models/relationship-options.model'; import { RelationshipOptions } from '../../../models/relationship-options.model';
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model'; import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
import { RemoteData } from '../../../../../../core/data/remote-data'; import { RemoteData } from '../../../../../../core/data/remote-data';
import { map, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { createSuccessfulRemoteDataObject } from '../../../../../testing/utils'; import { createSuccessfulRemoteDataObject } from '../../../../../testing/utils';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { Router } from '@angular/router';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
import { RouteService } from '../../../../../services/route.service';
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
import { size } from 'memory-cache';
import { PageInfo } from '../../../../../../core/shared/page-info.model';
@Component({ @Component({
selector: 'ds-dynamic-lookup-relation-selection-tab', selector: 'ds-dynamic-lookup-relation-selection-tab',
@@ -24,24 +30,54 @@ import { PaginatedList } from '../../../../../../core/data/paginated-list';
export class DsDynamicLookupRelationSelectionTabComponent { export class DsDynamicLookupRelationSelectionTabComponent {
@Input() label: string; @Input() label: string;
@Input() relationship: RelationshipOptions;
@Input() listId: string; @Input() listId: string;
@Input() itemRD$;
@Input() repeatable: boolean; @Input() repeatable: boolean;
@Input() selection$: Observable<ListableObject[]>; @Input() selection$: Observable<ListableObject[]>;
@Input() selectionRD$: Observable<RemoteData<PaginatedList<ListableObject>>>; @Input() selectionRD$: Observable<RemoteData<PaginatedList<ListableObject>>>;
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
initialPagination = Object.assign(new PaginationComponentOptions(), { initialPagination = Object.assign(new PaginationComponentOptions(), {
id: 'submission-relation-list', id: 'submission-relation-list',
pageSize: 5 pageSize: 5
}); });
constructor() { constructor(private router: Router,
private searchConfigService: SearchConfigurationService) {
} }
ngOnInit() { ngOnInit() {
this.selectionRD$ = this.selection$.pipe( this.resetRoute();
take(1), this.selectionRD$ = this.searchConfigService.paginatedSearchOptions
map((selection) => createSuccessfulRemoteDataObject(new PaginatedList({} as any, selection))) .pipe(
); map((options: PaginatedSearchOptions) => options.pagination),
switchMap((pagination: PaginationComponentOptions) => {
return this.selection$.pipe(
take(1),
map((selected) => {
const offset = (pagination.currentPage - 1) * pagination.pageSize;
const end = (offset + pagination.pageSize) > selected.length ? selected.length : offset + pagination.pageSize;
const selection = selected.slice(offset, end);
const pageInfo = new PageInfo(
{
elementsPerPage: pagination.pageSize,
totalElements: selected.length,
currentPage: pagination.currentPage,
totalPages: Math.ceil(selected.length / pagination.pageSize)
});
return createSuccessfulRemoteDataObject(new PaginatedList(pageInfo, selection))
})
);
})
)
}
resetRoute() {
this.router.navigate([], {
queryParams: Object.assign({}, { page: 1, pageSize: this.initialPagination.pageSize }),
});
} }
} }

View File

@@ -0,0 +1,10 @@
<div class="setting-option page-size-settings mb-3 p-3">
<h5>{{ 'search.sidebar.settings.rpp' | translate}}</h5>
<select class="form-control" (change)="reloadRPP($event)">
<option *ngFor="let pageSizeOption of (paginationOptions$ | async).pageSizeOptions"
[value]="pageSizeOption"
[selected]="pageSizeOption === +(paginationOptions$ | async).pageSize ? 'selected': null">
{{pageSizeOption}}
</option>
</select>
</div>

View File

@@ -0,0 +1,3 @@
.setting-option {
border: 1px solid map-get($theme-colors, light);
}

View File

@@ -0,0 +1,52 @@
import { Component, Inject, Input, OnInit } from '@angular/core';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { Observable } from 'rxjs';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
import { PaginatedSearchOptions } from '../search/paginated-search-options.model';
import { map } from 'rxjs/operators';
@Component({
selector: 'ds-page-size-selector',
styleUrls: ['./page-size-selector.component.scss'],
templateUrl: './page-size-selector.component.html'
})
/**
* This component represents the part of the search sidebar that contains the general search settings.
*/
export class PageSizeSelectorComponent implements OnInit {
/**
* The configuration for the current paginated search results
*/
paginationOptions$: Observable<PaginationComponentOptions>;
constructor(private route: ActivatedRoute,
private router: Router,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigurationService: SearchConfigurationService) {
}
/**
* Initialize paginated search options
*/
ngOnInit(): void {
this.paginationOptions$ = this.searchConfigurationService.paginatedSearchOptions.pipe(map((options: PaginatedSearchOptions) => options.pagination));
}
/**
* Method to change the current page size (results per page)
* @param {Event} event Change event containing the new page size value
*/
reloadRPP(event: Event) {
const value = (event.target as HTMLInputElement).value;
const navigationExtras: NavigationExtras = {
queryParams: {
pageSize: value,
page: 1
},
queryParamsHandling: 'merge'
};
this.router.navigate([], navigationExtras);
}
}

View File

@@ -1,13 +1,9 @@
<div class="row mb-3 mb-md-1"> <ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)"><!--Do not remove this to prevent uneven spacing
<div class="labels col-sm-9 offset-sm-3">
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)"><!--Do not remove this to prevent uneven spacing
--><a *ngFor="let values of (appliedFilters | async)[key]" --><a *ngFor="let values of (appliedFilters | async)[key]"
class="badge badge-primary mr-1 mb-1 text-capitalize" class="badge badge-primary mr-1 mb-1 text-capitalize"
[routerLink]="getSearchLink()" [routerLink]="getSearchLink()"
[queryParams]="(getRemoveParams(key, values) | async)" queryParamsHandling="merge"> [queryParams]="(getRemoveParams(key, values) | async)" queryParamsHandling="merge">
{{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(values)}} {{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(values)}}
<span> ×</span> <span> ×</span>
</a><!--Do not remove this to prevent uneven spacing </a><!--Do not remove this to prevent uneven spacing
--></ng-container> --></ng-container>
</div>
</div>

View File

@@ -1,24 +1,14 @@
<ng-container *ngVar="(searchOptions$ | async) as config"> <ng-container *ngVar="(searchOptions$ | async) as config">
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3> <h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
<div *ngIf="config?.sort" class="setting-option result-order-settings mb-3 p-3"> <div *ngIf="config?.sort" class="setting-option result-order-settings mb-3 p-3">
<h5>{{ 'search.sidebar.settings.sort-by' | translate}}</h5> <h5>{{ 'search.sidebar.settings.sort-by' | translate}}</h5>
<select class="form-control" (change)="reloadOrder($event)"> <select class="form-control" (change)="reloadOrder($event)">
<option *ngFor="let sortOption of searchOptionPossibilities" <option *ngFor="let sortOption of searchOptionPossibilities"
[value]="sortOption.field + ',' + sortOption.direction.toString()" [value]="sortOption.field + ',' + sortOption.direction.toString()"
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null"> [selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}} {{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
</option>
</select>
</div>
<div class="setting-option page-size-settings mb-3 p-3">
<h5>{{ 'search.sidebar.settings.rpp' | translate}}</h5>
<select class="form-control" (change)="reloadRPP($event)">
<option *ngFor="let pageSizeOption of config?.pagination.pageSizeOptions"
[value]="pageSizeOption"
[selected]="pageSizeOption === +config?.pagination.pageSize ? 'selected': null">
{{pageSizeOption}}
</option> </option>
</select> </select>
</div> </div>
<ds-page-size-selector></ds-page-size-selector>
</ng-container> </ng-container>

View File

@@ -18,12 +18,6 @@ import { currentPath } from '../../utils/route.utils';
* This component represents the part of the search sidebar that contains the general search settings. * This component represents the part of the search sidebar that contains the general search settings.
*/ */
export class SearchSettingsComponent implements OnInit { export class SearchSettingsComponent implements OnInit {
/**
* True when the search component should show results on the current page
*/
@Input() inPlaceSearch;
/** /**
* The configuration for the current paginated search results * The configuration for the current paginated search results
*/ */
@@ -47,22 +41,6 @@ export class SearchSettingsComponent implements OnInit {
this.searchOptions$ = this.searchConfigurationService.paginatedSearchOptions; this.searchOptions$ = this.searchConfigurationService.paginatedSearchOptions;
} }
/**
* Method to change the current page size (results per page)
* @param {Event} event Change event containing the new page size value
*/
reloadRPP(event: Event) {
const value = (event.target as HTMLInputElement).value;
const navigationExtras: NavigationExtras = {
queryParams: {
pageSize: value,
page: 1
},
queryParamsHandling: 'merge'
};
this.router.navigate(this.getSearchLinkParts(), navigationExtras);
}
/** /**
* Method to change the current sort field and direction * Method to change the current sort field and direction
* @param {Event} event Change event containing the sort direction and sort field * @param {Event} event Change event containing the sort direction and sort field
@@ -77,26 +55,6 @@ export class SearchSettingsComponent implements OnInit {
}, },
queryParamsHandling: 'merge' queryParamsHandling: 'merge'
}; };
this.router.navigate(this.getSearchLinkParts(), navigationExtras); this.router.navigate([], navigationExtras);
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
if (this.inPlaceSearch) {
return currentPath(this.router);
}
return this.service.getSearchLink();
}
/**
* @returns {string[]} The base path to the search page, or the current page when inPlaceSearch is true, split in separate pieces
*/
public getSearchLinkParts(): string[] {
if (this.service) {
return [];
}
return this.getSearchLink().split('/');
} }
} }

View File

@@ -12,7 +12,7 @@
<div class="sidebar-content"> <div class="sidebar-content">
<ds-search-switch-configuration [inPlaceSearch]="inPlaceSearch" *ngIf="configurationList" [configurationList]="configurationList"></ds-search-switch-configuration> <ds-search-switch-configuration [inPlaceSearch]="inPlaceSearch" *ngIf="configurationList" [configurationList]="configurationList"></ds-search-switch-configuration>
<ds-search-filters [inPlaceSearch]="inPlaceSearch"></ds-search-filters> <ds-search-filters [inPlaceSearch]="inPlaceSearch"></ds-search-filters>
<ds-search-settings [inPlaceSearch]="inPlaceSearch"></ds-search-settings> <ds-search-settings></ds-search-settings>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -162,6 +162,7 @@ import { SearchAuthorityFilterComponent } from './search/search-filters/search-f
import { DsDynamicDisabledComponent } from './form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component'; import { DsDynamicDisabledComponent } from './form/builder/ds-dynamic-form-ui/models/disabled/dynamic-disabled.component';
import { DsDynamicLookupRelationSearchTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component'; import { DsDynamicLookupRelationSearchTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/search-tab/dynamic-lookup-relation-search-tab.component';
import { DsDynamicLookupRelationSelectionTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component'; import { DsDynamicLookupRelationSelectionTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/selection-tab/dynamic-lookup-relation-selection-tab.component';
import { PageSizeSelectorComponent } from './page-size-selector/page-size-selector.component';
const MODULES = [ const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here // Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -305,6 +306,7 @@ const COMPONENTS = [
SearchFacetRangeOptionComponent, SearchFacetRangeOptionComponent,
SearchSwitchConfigurationComponent, SearchSwitchConfigurationComponent,
SearchAuthorityFilterComponent, SearchAuthorityFilterComponent,
PageSizeSelectorComponent
]; ];
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [