created tabs, working on concat field lookups

This commit is contained in:
lotte
2019-08-30 16:17:43 +02:00
parent addc6618ba
commit 5142915e83
13 changed files with 327 additions and 272 deletions

View File

@@ -56,11 +56,10 @@ export class RelationshipTypeService {
/* Flatten the page so we can treat it like an observable */
switchMap((typeListRD: RemoteData<PaginatedList<RelationshipType>>) => typeListRD.payload.page),
switchMap((type: RelationshipType) => {
if (type.leftLabel === label) return this.checkType(type, firstType, secondType);
else if (type.rightLabel === label) return this.checkType(type, secondType, firstType);
if (type.rightLabel === label) return this.checkType(type, firstType, secondType);
else if (type.leftLabel === label) return this.checkType(type, secondType, firstType);
else return [];
}),
tap((t) => console.log(t))
);
}

View File

@@ -6,5 +6,5 @@ export class MockResponseMap extends Map<string, any> {};
export const MOCK_RESPONSE_MAP: InjectionToken<MockResponseMap> = new InjectionToken<MockResponseMap>('mockResponseMap');
export const mockResponseMap: MockResponseMap = new Map([
[ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ]
// [ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ]
]);

View File

@@ -4,7 +4,8 @@ import {
ComponentFactoryResolver,
ContentChildren,
EventEmitter,
Input, NgZone,
Input,
NgZone,
OnChanges,
OnInit,
Output,
@@ -69,7 +70,7 @@ import { DsDynamicFormArrayComponent } from './models/array-group/dynamic-form-a
import { DsDynamicRelationGroupComponent } from './models/relation-group/dynamic-relation-group.components';
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './models/relation-group/dynamic-relation-group.model';
import { DsDatePickerInlineComponent } from './models/date-picker-inline/dynamic-date-picker-inline.component';
import { map, switchMap, take, tap } from 'rxjs/operators';
import { map, switchMap, take } from 'rxjs/operators';
import { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
import { Observable } from 'rxjs';
import { SearchResult } from '../../../search/search-result.model';
@@ -81,14 +82,7 @@ import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.c
import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
import { ItemViewMode } from '../../../items/item-type-decorator';
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { relationship } from '../../../../core/cache/builders/build-decorators';
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { RelationshipOptions } from '../models/relationship-options.model';
import { DsDynamicInputModel } from './models/ds-dynamic-input.model';
import { getSucceededRemoteData } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';

View File

@@ -2,6 +2,8 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelC
import { isNotEmpty } from '../../../../empty.util';
import { DsDynamicInputModel } from './ds-dynamic-input.model';
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
import { RelationshipOptions } from '../../models/relationship-options.model';
export const CONCAT_GROUP_SUFFIX = '_CONCAT_GROUP';
export const CONCAT_FIRST_INPUT_SUFFIX = '_CONCAT_FIRST_INPUT';
@@ -9,12 +11,19 @@ export const CONCAT_SECOND_INPUT_SUFFIX = '_CONCAT_SECOND_INPUT';
export interface DynamicConcatModelConfig extends DynamicFormGroupModelConfig {
separator: string;
value?: any;
workspaceItem: WorkspaceItem;
relationship?: RelationshipOptions;
repeatable: boolean;
}
export class DynamicConcatModel extends DynamicFormGroupModel {
@serializable() separator: string;
@serializable() hasLanguages = false;
@serializable() workspaceItem: WorkspaceItem;
@serializable() relationship?: RelationshipOptions;
@serializable() repeatable?: boolean;
isCustomGroup = true;
constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) {
@@ -22,6 +31,9 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
super(config, layout);
this.separator = config.separator + ' ';
this.relationship = config.relationship;
this.workspaceItem = config.workspaceItem;
this.repeatable = config.repeatable;
}
get value() {

View File

@@ -5,77 +5,22 @@
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body" *ngVar="(resultsRD$ | async) as resultsRD">
<div class="row">
<ds-search-sidebar class="col-4" id="search-sidebar"
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
[inPlaceSearch]="true"></ds-search-sidebar>
<div class="col-8">
<form class="input-group mb-3" #queryForm="ngForm"
(ngSubmit)="search(queryForm.value.query)">
<input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.placeholder' | translate"
[ngModel]="searchQuery">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search' | translate) }}</button>
</div>
</form>
<div *ngIf="repeatable" class="position-absolute">
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<!-- In theory we don't need separate checkboxes for this,
but I wasn't able to get this to work correctly without them.
Checkboxes that are in the indeterminate state always switch to checked when clicked
This seemed like the cleanest and clearest solution to solve this issue for now.
-->
<input *ngIf="!allSelected && !(someSelected$ | async)"
type="checkbox"
[indeterminate]="false"
(change)="selectAll()">
<input *ngIf="!allSelected && (someSelected$ | async)"
type="checkbox"
[indeterminate]="true"
(change)="deselectAll()">
<input *ngIf="allSelected" type="checkbox"
[checked]="true"
(change)="deselectAll()">
</div>
</div>
<div ngbDropdown class="input-group-append">
<button *ngIf="selectAllLoading" type="button"
class="btn btn-outline-secondary rounded-right">
<span class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.loading' | translate) }}</span>
</button>
<button id="resultdropdown" type="button"
ngbDropdownToggle
class="btn btn-outline-secondary dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"
[hidden]="selectAllLoading">
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.toggle-dropdown' | translate) }}</span>
</button>
<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" (click)="deselectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.deselect-page' | translate) }}</button>
<button class="dropdown-item" (click)="selectAll()">{{ ('submission.sections.describe.relationship-lookup.select-all' | translate) }}</button>
<button class="dropdown-item" (click)="deselectAll()">{{ ('submission.sections.describe.relationship-lookup.deselect-all' | translate) }}</button>
</div>
</div>
</div>
</div>
<ds-search-results [searchResults]="resultsRD"
[sortConfig]="this.searchConfig?.sort"
[searchConfig]="this.searchConfig"
[selectable]="true"
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
(deselectObject)="deselect($event)"
(selectObject)="select($event)"
>
</ds-search-results>
</div>
</div>
<div class="modal-body">
<ngb-tabset>
<ngb-tab title="Search">
<ng-template ngbTabContent>
<ds-dynamic-lookup-relation-search-tab
[label]="label"
[itemRD$]="itemRD$"
[selection$]="selection$"
[listId]="listId"
[relationship]="relationship"
[repeatable]="repeatable">
</ds-dynamic-lookup-relation-search-tab>
</ng-template>
</ngb-tab>
</ngb-tabset>
</div>
<div class="modal-footer">
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate:{size: (selection$ | async)?.length || 0}) }}</small>

View File

@@ -1,11 +1,3 @@
.result-list-element {
flex: 1;
}
.modal-footer {
justify-content: space-between;
}
.position-absolute {
right: $spacer;
}

View File

@@ -1,32 +1,14 @@
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { SearchResult } from '../../../../search/search-result.model';
import { RemoteData } from '../../../../../core/data/remote-data';
import { from, Observable, ReplaySubject } from 'rxjs';
import { SearchService } from '../../../../../core/shared/search/search.service';
import { PaginatedSearchOptions } from '../../../../search/paginated-search-options.model';
import { DSpaceObject } from '../../../../../core/shared/dspace-object.model';
import { PaginationComponentOptions } from '../../../../pagination/pagination-component-options.model';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { hasValue, hasValueOperator, isNotEmpty } from '../../../../empty.util';
import { concat, map, mergeMap, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { Router } from '@angular/router';
import { hasValue } from '../../../../empty.util';
import { map } from 'rxjs/operators';
import { SEARCH_CONFIG_SERVICE } from '../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../core/shared/search/search-configuration.service';
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
import { SelectableListState } from '../../../../object-list/selectable-list/selectable-list.reducer';
import { ListableObject } from '../../../../object-collection/shared/listable-object.model';
import { RouteService } from '../../../../services/route.service';
import { getSucceededRemoteData } from '../../../../../core/shared/operators';
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
import { RelationshipType } from '../../../../../core/shared/item-relationships/relationship-type.model';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { Item } from '../../../../../core/shared/item.model';
import { RelationshipOptions } from '../../models/relationship-options.model';
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
import { Store } from '@ngrx/store';
import { AppState } from '../../../../../app.reducer';
import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.actions';
@Component({
selector: 'ds-dynamic-lookup-relation-modal',
@@ -40,172 +22,26 @@ import { AddRelationshipAction, RemoveRelationshipAction } from './relationship.
]
})
export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy {
export class DsDynamicLookupRelationModalComponent implements OnInit {
label: string;
relationship: RelationshipOptions;
listId: string;
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
searchConfig: PaginatedSearchOptions;
repeatable: boolean;
searchQuery;
allSelected: boolean;
someSelected$: Observable<boolean>;
selectAllLoading: boolean;
subscription;
initialPagination = Object.assign(new PaginationComponentOptions(), {
id: 'submission-relation-list',
pageSize: 5
});
selection$: Observable<ListableObject[]>;
itemRD$;
repeatable: boolean;
selection$: Observable<ListableObject[]>;
constructor(
public modal: NgbActiveModal,
private searchService: SearchService,
private router: Router,
private selectableListService: SelectableListService,
private searchConfigService: SearchConfigurationService,
private routeService: RouteService,
private relationshipService: RelationshipService,
private relationshipTypeService: RelationshipTypeService,
private zone: NgZone,
private store: Store<AppState>
) {
}
ngOnInit(): void {
this.resetRoute();
this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
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.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
map((options) => {
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.relationship.filter, configuration: this.relationship.searchConfiguration })
}),
switchMap((options) => {
this.searchConfig = options;
return this.searchService.search(options).pipe(
/* Make sure to only listen to the first x results, until loading is finished */
/* TODO: in Rxjs 6.4.0 and up, we can replace this by takeWhile(predicate, true) - see https://stackoverflow.com/a/44644237 */
multicast(
() => new ReplaySubject(1),
subject => subject.pipe(
takeWhile((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => rd.isLoading),
concat(subject.pipe(take(1))
)
)
) as any
)
})
);
}
search(query: string) {
this.allSelected = false;
this.searchQuery = query;
this.resetRoute();
}
close() {
this.modal.close();
}
resetRoute() {
this.router.navigate([], {
queryParams: Object.assign({}, { page: 1, query: this.searchQuery, pageSize: this.initialPagination.pageSize }),
});
}
selectPage(page: SearchResult<Item>[]) {
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0)
this.select(...filteredPage);
});
this.selectableListService.select(this.listId, page);
}
deselectPage(page: SearchResult<Item>[]) {
this.allSelected = false;
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0)
this.deselect(...filteredPage);
});
this.selectableListService.deselect(this.listId, page);
}
selectAll() {
this.allSelected = true;
this.selectAllLoading = true;
const fullPagination = Object.assign(new PaginationComponentOptions(), {
query: this.searchQuery,
currentPage: 1,
pageSize: 9999
});
const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
const results$ = this.searchService.search(fullSearchConfig) as Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
results$.pipe(
getSucceededRemoteData(),
map((resultsRD) => resultsRD.payload.page),
tap(() => this.selectAllLoading = false),
).subscribe((results) => {
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredResults = results.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
this.select(...filteredResults);
});
this.selectableListService.select(this.listId, results);
}
);
}
deselectAll() {
this.allSelected = false;
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => this.deselect(...selection));
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 {
if (hasValue(this.subscription)
) {
this.subscription.unsubscribe();
}
}
}

View File

@@ -69,21 +69,22 @@ export class RelationshipEffects {
private createIdentifier(item1: Item, item2: Item, relationshipType: string): string {
return `${item1.uuid}-${item2.uuid}-${relationshipType}$`;
return `${item1.uuid}-${item2.uuid}-${relationshipType}`;
}
private addRelationship(item1: Item, item2: Item, relationshipType: string) {
const type1: string = item1.firstMetadataValue('relationship.type');
// const type1: string = item1.firstMetadataValue('relationship.type');
const type1: string = 'JournalVolume';
const type2: string = item2.firstMetadataValue('relationship.type');
return this.relationshipTypeService.getRelationshipTypeByLabelAndTypes(relationshipType, type1, type2)
.pipe(
mergeMap((type: RelationshipType) => {
const isSwitched = type.rightLabel === relationshipType;
const isSwitched = type.leftLabel === relationshipType;
if (isSwitched) {
return this.relationshipService.addRelationship(type.id, cloneDeep(item2), cloneDeep(item1));
return this.relationshipService.addRelationship(type.id, item2, item1);
} else {
return this.relationshipService.addRelationship(type.id, cloneDeep(item1), cloneDeep(item2));
return this.relationshipService.addRelationship(type.id, item1, item2);
}
}
)

View File

@@ -0,0 +1,70 @@
<div class="row" *ngVar="(resultsRD$ | async) as resultsRD">
<ds-search-sidebar class="col-4" id="search-sidebar"
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
[inPlaceSearch]="true"></ds-search-sidebar>
<div class="col-8">
<form class="input-group mb-3" #queryForm="ngForm"
(ngSubmit)="search(queryForm.value.query)">
<input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.placeholder' | translate"
[ngModel]="searchQuery">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search' | translate) }}</button>
</div>
</form>
<div *ngIf="repeatable" class="position-absolute">
<div class="input-group mb-3">
<div class="input-group-prepend">
<div class="input-group-text">
<!-- In theory we don't need separate checkboxes for this,
but I wasn't able to get this to work correctly without them.
Checkboxes that are in the indeterminate state always switch to checked when clicked
This seemed like the cleanest and clearest solution to solve this issue for now.
-->
<input *ngIf="!allSelected && !(someSelected$ | async)"
type="checkbox"
[indeterminate]="false"
(change)="selectAll()">
<input *ngIf="!allSelected && (someSelected$ | async)"
type="checkbox"
[indeterminate]="true"
(change)="deselectAll()">
<input *ngIf="allSelected" type="checkbox"
[checked]="true"
(change)="deselectAll()">
</div>
</div>
<div ngbDropdown class="input-group-append">
<button *ngIf="selectAllLoading" type="button"
class="btn btn-outline-secondary rounded-right">
<span class="spinner-border spinner-border-sm" role="status"
aria-hidden="true"></span>
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.loading' | translate) }}</span>
</button>
<button id="resultdropdown" type="button"
ngbDropdownToggle
class="btn btn-outline-secondary dropdown-toggle-split"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"
[hidden]="selectAllLoading">
<span class="sr-only">{{ ('submission.sections.describe.relationship-lookup.toggle-dropdown' | translate) }}</span>
</button>
<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" (click)="deselectPage(resultsRD?.payload?.page)">{{ ('submission.sections.describe.relationship-lookup.deselect-page' | translate) }}</button>
<button class="dropdown-item" (click)="selectAll()">{{ ('submission.sections.describe.relationship-lookup.select-all' | translate) }}</button>
<button class="dropdown-item" (click)="deselectAll()">{{ ('submission.sections.describe.relationship-lookup.deselect-all' | translate) }}</button>
</div>
</div>
</div>
</div>
<ds-search-results [searchResults]="resultsRD"
[sortConfig]="this.searchConfig?.sort"
[searchConfig]="this.searchConfig"
[selectable]="true"
[selectionConfig]="{ repeatable: repeatable, listId: listId }"
(deselectObject)="deselect($event)"
(selectObject)="select($event)"
>
</ds-search-results>
</div>
</div>

View File

@@ -0,0 +1,204 @@
import { Component, Input, NgZone, OnDestroy, OnInit } from '@angular/core';
import { SEARCH_CONFIG_SERVICE } from '../../../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
import { Item } from '../../../../../../core/shared/item.model';
import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model';
import { SearchResult } from '../../../../../search/search-result.model';
import { PaginatedList } from '../../../../../../core/data/paginated-list';
import { RemoteData } from '../../../../../../core/data/remote-data';
import { Observable, ReplaySubject } from 'rxjs';
import { RelationshipOptions } from '../../../models/relationship-options.model';
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
import { SearchService } from '../../../../../../core/shared/search/search.service';
import { Router } from '@angular/router';
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.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 { hasValue, isNotEmpty } from '../../../../../empty.util';
import { concat, map, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
import { getSucceededRemoteData } from '../../../../../../core/shared/operators';
import { AddRelationshipAction, RemoveRelationshipAction } from '../relationship.actions';
@Component({
selector: 'ds-dynamic-lookup-relation-search-tab',
styleUrls: ['./dynamic-lookup-relation-search-tab.component.scss'],
templateUrl: './dynamic-lookup-relation-search-tab.component.html',
providers: [
{
provide: SEARCH_CONFIG_SERVICE,
useClass: SearchConfigurationService
}
]
})
export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDestroy {
@Input() label: string;
@Input() relationship: RelationshipOptions;
@Input() listId: string;
@Input() itemRD$;
@Input() repeatable: boolean;
@Input() selection$: Observable<ListableObject[]>;
resultsRD$: Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
searchConfig: PaginatedSearchOptions;
searchQuery;
allSelected: boolean;
someSelected$: Observable<boolean>;
selectAllLoading: boolean;
subscription;
initialPagination = Object.assign(new PaginationComponentOptions(), {
id: 'submission-relation-list',
pageSize: 5
});
constructor(
private searchService: SearchService,
private router: Router,
private selectableListService: SelectableListService,
private searchConfigService: SearchConfigurationService,
private routeService: RouteService,
private relationshipService: RelationshipService,
private relationshipTypeService: RelationshipTypeService,
private zone: NgZone,
private store: Store<AppState>
) {
}
ngOnInit(): void {
this.resetRoute();
this.routeService.setParameter('fixedFilterQuery', this.relationship.filter);
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.resultsRD$ = this.searchConfigService.paginatedSearchOptions.pipe(
map((options) => {
return Object.assign(new PaginatedSearchOptions({}), options, { fixedFilter: this.relationship.filter, configuration: this.relationship.searchConfiguration })
}),
switchMap((options) => {
this.searchConfig = options;
return this.searchService.search(options).pipe(
/* Make sure to only listen to the first x results, until loading is finished */
/* TODO: in Rxjs 6.4.0 and up, we can replace this with takeWhile(predicate, true) - see https://stackoverflow.com/a/44644237 */
multicast(
() => new ReplaySubject(1),
subject => subject.pipe(
takeWhile((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => rd.isLoading),
concat(subject.pipe(take(1))
)
)
) as any
)
})
);
}
search(query: string) {
this.allSelected = false;
this.searchQuery = query;
this.resetRoute();
}
resetRoute() {
this.router.navigate([], {
queryParams: Object.assign({}, { page: 1, query: this.searchQuery, pageSize: this.initialPagination.pageSize }),
});
}
selectPage(page: SearchResult<Item>[]) {
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0)
this.select(...filteredPage);
});
this.selectableListService.select(this.listId, page);
}
deselectPage(page: SearchResult<Item>[]) {
this.allSelected = false;
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredPage = page.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) >= 0)
this.deselect(...filteredPage);
});
this.selectableListService.deselect(this.listId, page);
}
selectAll() {
this.allSelected = true;
this.selectAllLoading = true;
const fullPagination = Object.assign(new PaginationComponentOptions(), {
query: this.searchQuery,
currentPage: 1,
pageSize: 9999
});
const fullSearchConfig = Object.assign(this.searchConfig, { pagination: fullPagination });
const results$ = this.searchService.search(fullSearchConfig) as Observable<RemoteData<PaginatedList<SearchResult<Item>>>>;
results$.pipe(
getSucceededRemoteData(),
map((resultsRD) => resultsRD.payload.page),
tap(() => this.selectAllLoading = false),
).subscribe((results) => {
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => {
const filteredResults = results.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
this.select(...filteredResults);
});
this.selectableListService.select(this.listId, results);
}
);
}
deselectAll() {
this.allSelected = false;
this.selection$
.pipe(take(1))
.subscribe((selection: SearchResult<Item>[]) => this.deselect(...selection));
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 {
if (hasValue(this.subscription)
) {
this.subscription.unsubscribe();
}
}
}

View File

@@ -116,7 +116,6 @@ export class ObjectListComponent {
if (value) {
this.selectionService.selectSingle(this.selectionConfig.listId, object);
this.selectObject.emit(object);
} else {
this.selectionService.deselectSingle(this.selectionConfig.listId, object);
this.deselectObject.emit(object);
@@ -139,7 +138,5 @@ export class ObjectListComponent {
this.selectObject.emit(object);
}
});
}
}

View File

@@ -160,6 +160,7 @@ import { SearchFacetRangeOptionComponent } from './search/search-filters/search-
import { SearchSwitchConfigurationComponent } from './search/search-switch-configuration/search-switch-configuration.component';
import { SearchAuthorityFilterComponent } from './search/search-filters/search-filter/search-authority-filter/search-authority-filter.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';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -360,7 +361,8 @@ const ENTRY_COMPONENTS = [
SearchFacetOptionComponent,
SearchFacetSelectedOptionComponent,
SearchFacetRangeOptionComponent,
SearchAuthorityFilterComponent
SearchAuthorityFilterComponent,
DsDynamicLookupRelationSearchTabComponent
];
const SHARED_ITEM_PAGE_COMPONENTS = [