mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
created tabs, working on concat field lookups
This commit is contained in:
@@ -56,11 +56,10 @@ export class RelationshipTypeService {
|
|||||||
/* Flatten the page so we can treat it like an observable */
|
/* Flatten the page so we can treat it like an observable */
|
||||||
switchMap((typeListRD: RemoteData<PaginatedList<RelationshipType>>) => typeListRD.payload.page),
|
switchMap((typeListRD: RemoteData<PaginatedList<RelationshipType>>) => typeListRD.payload.page),
|
||||||
switchMap((type: RelationshipType) => {
|
switchMap((type: RelationshipType) => {
|
||||||
if (type.leftLabel === label) return this.checkType(type, firstType, secondType);
|
if (type.rightLabel === label) return this.checkType(type, firstType, secondType);
|
||||||
else if (type.rightLabel === label) return this.checkType(type, secondType, firstType);
|
else if (type.leftLabel === label) return this.checkType(type, secondType, firstType);
|
||||||
else return [];
|
else return [];
|
||||||
}),
|
}),
|
||||||
tap((t) => console.log(t))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,5 +6,5 @@ export class MockResponseMap extends Map<string, any> {};
|
|||||||
export const MOCK_RESPONSE_MAP: InjectionToken<MockResponseMap> = new InjectionToken<MockResponseMap>('mockResponseMap');
|
export const MOCK_RESPONSE_MAP: InjectionToken<MockResponseMap> = new InjectionToken<MockResponseMap>('mockResponseMap');
|
||||||
|
|
||||||
export const mockResponseMap: MockResponseMap = new Map([
|
export const mockResponseMap: MockResponseMap = new Map([
|
||||||
[ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ]
|
// [ '/config/submissionforms/traditionalpageone', mockSubmissionResponse ]
|
||||||
]);
|
]);
|
||||||
|
@@ -4,7 +4,8 @@ import {
|
|||||||
ComponentFactoryResolver,
|
ComponentFactoryResolver,
|
||||||
ContentChildren,
|
ContentChildren,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input, NgZone,
|
Input,
|
||||||
|
NgZone,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
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 { 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 { 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 { 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 { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { SearchResult } from '../../../search/search-result.model';
|
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 { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-disabled.model';
|
||||||
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
|
||||||
import { ItemViewMode } from '../../../items/item-type-decorator';
|
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 { 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 { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
@@ -2,6 +2,8 @@ import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelC
|
|||||||
import { isNotEmpty } from '../../../../empty.util';
|
import { isNotEmpty } from '../../../../empty.util';
|
||||||
import { DsDynamicInputModel } from './ds-dynamic-input.model';
|
import { DsDynamicInputModel } from './ds-dynamic-input.model';
|
||||||
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.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_GROUP_SUFFIX = '_CONCAT_GROUP';
|
||||||
export const CONCAT_FIRST_INPUT_SUFFIX = '_CONCAT_FIRST_INPUT';
|
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 {
|
export interface DynamicConcatModelConfig extends DynamicFormGroupModelConfig {
|
||||||
separator: string;
|
separator: string;
|
||||||
|
value?: any;
|
||||||
|
workspaceItem: WorkspaceItem;
|
||||||
|
relationship?: RelationshipOptions;
|
||||||
|
repeatable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicConcatModel extends DynamicFormGroupModel {
|
export class DynamicConcatModel extends DynamicFormGroupModel {
|
||||||
|
|
||||||
@serializable() separator: string;
|
@serializable() separator: string;
|
||||||
@serializable() hasLanguages = false;
|
@serializable() hasLanguages = false;
|
||||||
|
@serializable() workspaceItem: WorkspaceItem;
|
||||||
|
@serializable() relationship?: RelationshipOptions;
|
||||||
|
@serializable() repeatable?: boolean;
|
||||||
isCustomGroup = true;
|
isCustomGroup = true;
|
||||||
|
|
||||||
constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) {
|
constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) {
|
||||||
@@ -22,6 +31,9 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
|
|||||||
super(config, layout);
|
super(config, layout);
|
||||||
|
|
||||||
this.separator = config.separator + ' ';
|
this.separator = config.separator + ' ';
|
||||||
|
this.relationship = config.relationship;
|
||||||
|
this.workspaceItem = config.workspaceItem;
|
||||||
|
this.repeatable = config.repeatable;
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
|
@@ -5,77 +5,22 @@
|
|||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" *ngVar="(resultsRD$ | async) as resultsRD">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<ngb-tabset>
|
||||||
<ds-search-sidebar class="col-4" id="search-sidebar"
|
<ngb-tab title="Search">
|
||||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
<ng-template ngbTabContent>
|
||||||
[inPlaceSearch]="true"></ds-search-sidebar>
|
<ds-dynamic-lookup-relation-search-tab
|
||||||
<div class="col-8">
|
[label]="label"
|
||||||
<form class="input-group mb-3" #queryForm="ngForm"
|
[itemRD$]="itemRD$"
|
||||||
(ngSubmit)="search(queryForm.value.query)">
|
[selection$]="selection$"
|
||||||
<input type="text" class="form-control" name="query" [placeholder]="'submission.sections.describe.relationship-lookup.placeholder' | translate"
|
[listId]="listId"
|
||||||
[ngModel]="searchQuery">
|
[relationship]="relationship"
|
||||||
<div class="input-group-append">
|
[repeatable]="repeatable">
|
||||||
<button class="btn btn-outline-secondary" type="submit">{{ ('submission.sections.describe.relationship-lookup.search' | translate) }}</button>
|
</ds-dynamic-lookup-relation-search-tab>
|
||||||
</div>
|
</ng-template>
|
||||||
</form>
|
</ngb-tab>
|
||||||
<div *ngIf="repeatable" class="position-absolute">
|
</ngb-tabset>
|
||||||
<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>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate:{size: (selection$ | async)?.length || 0}) }}</small>
|
<small>{{ ('submission.sections.describe.relationship-lookup.selected' | translate:{size: (selection$ | async)?.length || 0}) }}</small>
|
||||||
|
@@ -1,11 +1,3 @@
|
|||||||
.result-list-element {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.position-absolute {
|
|
||||||
right: $spacer;
|
|
||||||
}
|
|
@@ -1,32 +1,14 @@
|
|||||||
import { Component, NgZone, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { PaginatedList } from '../../../../../core/data/paginated-list';
|
import { Observable } from 'rxjs';
|
||||||
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 { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { hasValue, hasValueOperator, isNotEmpty } from '../../../../empty.util';
|
import { hasValue } from '../../../../empty.util';
|
||||||
import { concat, map, mergeMap, multicast, switchMap, take, takeWhile, tap } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
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 { 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 { 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({
|
@Component({
|
||||||
selector: 'ds-dynamic-lookup-relation-modal',
|
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;
|
label: string;
|
||||||
relationship: RelationshipOptions;
|
relationship: RelationshipOptions;
|
||||||
listId: string;
|
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$;
|
itemRD$;
|
||||||
|
repeatable: boolean;
|
||||||
|
selection$: Observable<ListableObject[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public modal: NgbActiveModal,
|
public modal: NgbActiveModal,
|
||||||
private searchService: SearchService,
|
|
||||||
private router: Router,
|
|
||||||
private selectableListService: SelectableListService,
|
private selectableListService: SelectableListService,
|
||||||
private searchConfigService: SearchConfigurationService,
|
|
||||||
private routeService: RouteService,
|
|
||||||
private relationshipService: RelationshipService,
|
|
||||||
private relationshipTypeService: RelationshipTypeService,
|
|
||||||
private zone: NgZone,
|
|
||||||
private store: Store<AppState>
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
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.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() {
|
close() {
|
||||||
this.modal.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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -69,21 +69,22 @@ export class RelationshipEffects {
|
|||||||
|
|
||||||
|
|
||||||
private createIdentifier(item1: Item, item2: Item, relationshipType: string): string {
|
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) {
|
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');
|
const type2: string = item2.firstMetadataValue('relationship.type');
|
||||||
return this.relationshipTypeService.getRelationshipTypeByLabelAndTypes(relationshipType, type1, type2)
|
return this.relationshipTypeService.getRelationshipTypeByLabelAndTypes(relationshipType, type1, type2)
|
||||||
.pipe(
|
.pipe(
|
||||||
mergeMap((type: RelationshipType) => {
|
mergeMap((type: RelationshipType) => {
|
||||||
const isSwitched = type.rightLabel === relationshipType;
|
const isSwitched = type.leftLabel === relationshipType;
|
||||||
if (isSwitched) {
|
if (isSwitched) {
|
||||||
return this.relationshipService.addRelationship(type.id, cloneDeep(item2), cloneDeep(item1));
|
return this.relationshipService.addRelationship(type.id, item2, item1);
|
||||||
} else {
|
} else {
|
||||||
return this.relationshipService.addRelationship(type.id, cloneDeep(item1), cloneDeep(item2));
|
return this.relationshipService.addRelationship(type.id, item1, item2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@@ -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>
|
@@ -0,0 +1,3 @@
|
|||||||
|
.position-absolute {
|
||||||
|
right: $spacer;
|
||||||
|
}
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -116,7 +116,6 @@ export class ObjectListComponent {
|
|||||||
if (value) {
|
if (value) {
|
||||||
this.selectionService.selectSingle(this.selectionConfig.listId, object);
|
this.selectionService.selectSingle(this.selectionConfig.listId, object);
|
||||||
this.selectObject.emit(object);
|
this.selectObject.emit(object);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.selectionService.deselectSingle(this.selectionConfig.listId, object);
|
this.selectionService.deselectSingle(this.selectionConfig.listId, object);
|
||||||
this.deselectObject.emit(object);
|
this.deselectObject.emit(object);
|
||||||
@@ -139,7 +138,5 @@ export class ObjectListComponent {
|
|||||||
this.selectObject.emit(object);
|
this.selectObject.emit(object);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -160,6 +160,7 @@ import { SearchFacetRangeOptionComponent } from './search/search-filters/search-
|
|||||||
import { SearchSwitchConfigurationComponent } from './search/search-switch-configuration/search-switch-configuration.component';
|
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 { 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 { 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 = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -360,7 +361,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
SearchFacetOptionComponent,
|
SearchFacetOptionComponent,
|
||||||
SearchFacetSelectedOptionComponent,
|
SearchFacetSelectedOptionComponent,
|
||||||
SearchFacetRangeOptionComponent,
|
SearchFacetRangeOptionComponent,
|
||||||
SearchAuthorityFilterComponent
|
SearchAuthorityFilterComponent,
|
||||||
|
DsDynamicLookupRelationSearchTabComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
|
Reference in New Issue
Block a user