forked from hazza/dspace-angular
Merge pull request #534 from atmire/reorder-name-variants
Reordering related entities in the submission
This commit is contained in:
@@ -70,5 +70,4 @@ export class EditRelationshipComponent implements OnChanges {
|
|||||||
canUndo(): boolean {
|
canUndo(): boolean {
|
||||||
return this.fieldUpdate.changeType >= 0;
|
return this.fieldUpdate.changeType >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -5,26 +5,21 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { SearchPageRoutingModule } from './search-page-routing.module';
|
import { SearchPageRoutingModule } from './search-page-routing.module';
|
||||||
import { SearchComponent } from './search.component';
|
import { SearchComponent } from './search.component';
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { SidebarEffects } from '../shared/sidebar/sidebar-effects.service';
|
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
|
import { SearchTrackerComponent } from './search-tracker.component';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { SearchPageComponent } from './search-page.component';
|
import { SearchPageComponent } from './search-page.component';
|
||||||
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
|
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
|
||||||
import { SearchTrackerComponent } from './search-tracker.component';
|
|
||||||
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
|
|
||||||
const effects = [
|
|
||||||
SidebarEffects
|
|
||||||
];
|
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
SearchPageComponent,
|
SearchPageComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
ConfigurationSearchPageComponent,
|
ConfigurationSearchPageComponent,
|
||||||
SearchTrackerComponent,
|
SearchTrackerComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -32,7 +27,6 @@ const components = [
|
|||||||
SearchPageRoutingModule,
|
SearchPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EffectsModule.forFeature(effects),
|
|
||||||
CoreModule.forRoot(),
|
CoreModule.forRoot(),
|
||||||
StatisticsModule.forRoot(),
|
StatisticsModule.forRoot(),
|
||||||
],
|
],
|
||||||
|
@@ -9,10 +9,10 @@ import { RouteService } from '../core/services/route.service';
|
|||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { SearchSuccessResponse } from '../core/cache/response.models';
|
import { SearchSuccessResponse } from '../core/cache/response.models';
|
||||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { SearchQueryResponse } from '../shared/search/search-query-response.model';
|
import { SearchQueryResponse } from '../shared/search/search-query-response.model';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component triggers a page view statistic
|
* This component triggers a page view statistic
|
||||||
|
@@ -11,9 +11,9 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
|
|||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
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 { SearchResult } from '../shared/search/search-result.model';
|
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
|
import { SearchResult } from '../shared/search/search-result.model';
|
||||||
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { currentPath } from '../shared/utils/route.utils';
|
import { currentPath } from '../shared/utils/route.utils';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
@@ -17,6 +17,7 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import * as uuidv4 from 'uuid/v4';
|
import * as uuidv4 from 'uuid/v4';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||||
|
|
||||||
const endpoint = 'https://rest.api/core';
|
const endpoint = 'https://rest.api/core';
|
||||||
|
|
||||||
@@ -191,8 +192,7 @@ describe('DataService', () => {
|
|||||||
dso2.self = selfLink;
|
dso2.self = selfLink;
|
||||||
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
dso2.metadata = [{ key: 'dc.title', value: name2 }];
|
||||||
|
|
||||||
spyOn(service, 'findByHref').and.returnValues(observableOf(dso));
|
spyOn(service, 'findByHref').and.returnValue(createSuccessfulRemoteDataObject$(dso));
|
||||||
spyOn(objectCache, 'getObjectBySelfLink').and.returnValues(observableOf(dso));
|
|
||||||
spyOn(objectCache, 'addPatch');
|
spyOn(objectCache, 'addPatch');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -37,7 +37,7 @@ import { Operation } from 'fast-json-patch';
|
|||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { configureRequest, getResponseFromEntry } from '../shared/operators';
|
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
||||||
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
import { ErrorResponse, RestResponse } from '../cache/response.models';
|
||||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||||
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
|
||||||
@@ -248,8 +248,11 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
* @param {DSpaceObject} object The given object
|
* @param {DSpaceObject} object The given object
|
||||||
*/
|
*/
|
||||||
update(object: T): Observable<RemoteData<T>> {
|
update(object: T): Observable<RemoteData<T>> {
|
||||||
const oldVersion$ = this.objectCache.getObjectBySelfLink(object.self);
|
const oldVersion$ = this.findByHref(object.self);
|
||||||
return oldVersion$.pipe(take(1), mergeMap((oldVersion: NormalizedObject<T>) => {
|
return oldVersion$.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
mergeMap((oldVersion: T) => {
|
||||||
const operations = this.comparator.diff(oldVersion, object);
|
const operations = this.comparator.diff(oldVersion, object);
|
||||||
if (isNotEmpty(operations)) {
|
if (isNotEmpty(operations)) {
|
||||||
this.objectCache.addPatch(object.self, operations);
|
this.objectCache.addPatch(object.self, operations);
|
||||||
@@ -257,7 +260,6 @@ export abstract class DataService<T extends CacheableObject> {
|
|||||||
return this.findByHref(object.self);
|
return this.findByHref(object.self);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -123,8 +123,8 @@ describe('RelationshipService', () => {
|
|||||||
it('should clear the related items their cache', () => {
|
it('should clear the related items their cache', () => {
|
||||||
expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self);
|
expect(objectCache.remove).toHaveBeenCalledWith(relatedItem1.self);
|
||||||
expect(objectCache.remove).toHaveBeenCalledWith(item.self);
|
expect(objectCache.remove).toHaveBeenCalledWith(item.self);
|
||||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.self);
|
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(relatedItem1.uuid);
|
||||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.self);
|
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(item.uuid);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,46 +1,35 @@
|
|||||||
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { RequestService } from './request.service';
|
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
|
||||||
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
|
||||||
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
|
||||||
import {
|
|
||||||
configureRequest,
|
|
||||||
getRemoteDataPayload,
|
|
||||||
getResponseFromEntry,
|
|
||||||
getSucceededRemoteData
|
|
||||||
} from '../shared/operators';
|
|
||||||
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { RestResponse } from '../cache/response.models';
|
|
||||||
import { Item } from '../shared/item.model';
|
|
||||||
import { Relationship } from '../shared/item-relationships/relationship.model';
|
|
||||||
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
|
||||||
import { RemoteData } from './remote-data';
|
|
||||||
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, filter, map, mergeMap, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
|
import { compareArraysUsingIds, paginatedRelationsToItems, relationsToItems } from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
||||||
|
import { AppState, keySelector } from '../../app.reducer';
|
||||||
|
import { hasValue, hasValueOperator, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
|
import { ReorderableRelationship } from '../../shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
|
import { RemoveNameVariantAction, SetNameVariantAction } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
||||||
|
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { configureRequest, getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators';
|
||||||
|
import { SearchParam } from '../cache/models/search-param.model';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { DeleteRequest, FindListOptions, PostRequest, RestRequest } from './request.models';
|
||||||
|
import { RestResponse } from '../cache/response.models';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { RelationshipType } from '../shared/item-relationships/relationship-type.model';
|
||||||
|
import { RemoteData, RemoteDataState } from './remote-data';
|
||||||
import { PaginatedList } from './paginated-list';
|
import { PaginatedList } from './paginated-list';
|
||||||
import { ItemDataService } from './item-data.service';
|
import { ItemDataService } from './item-data.service';
|
||||||
import {
|
import { Relationship } from '../shared/item-relationships/relationship.model';
|
||||||
compareArraysUsingIds,
|
import { Item } from '../shared/item.model';
|
||||||
paginatedRelationsToItems,
|
|
||||||
relationsToItems
|
|
||||||
} from '../../+item-page/simple/item-types/shared/item-relationships-utils';
|
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
|
||||||
import { DataService } from './data.service';
|
import { DataService } from './data.service';
|
||||||
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
|
|
||||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
|
||||||
import { CoreState } from '../core.reducers';
|
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
|
||||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
||||||
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
|
||||||
import { SearchParam } from '../cache/models/search-param.model';
|
import { RequestService } from './request.service';
|
||||||
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { AppState, keySelector } from '../../app.reducer';
|
|
||||||
import { NameVariantListState } from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
|
||||||
import {
|
|
||||||
RemoveNameVariantAction,
|
|
||||||
SetNameVariantAction
|
|
||||||
} from '../../shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.actions';
|
|
||||||
|
|
||||||
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
const relationshipListsStateSelector = (state: AppState) => state.relationshipLists;
|
||||||
|
|
||||||
@@ -140,9 +129,9 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
this.findById(relationshipId).pipe(
|
this.findById(relationshipId).pipe(
|
||||||
getSucceededRemoteData(),
|
getSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
switchMap((relationship: Relationship) => combineLatest(
|
switchMap((rel: Relationship) => combineLatest(
|
||||||
relationship.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
|
rel.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
|
||||||
relationship.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload())
|
rel.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload())
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
take(1)
|
take(1)
|
||||||
@@ -158,10 +147,10 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
*/
|
*/
|
||||||
private removeRelationshipItemsFromCache(item) {
|
private removeRelationshipItemsFromCache(item) {
|
||||||
this.objectCache.remove(item.self);
|
this.objectCache.remove(item.self);
|
||||||
this.requestService.removeByHrefSubstring(item.self);
|
this.requestService.removeByHrefSubstring(item.uuid);
|
||||||
combineLatest(
|
combineLatest(
|
||||||
this.objectCache.hasBySelfLinkObservable(item.self),
|
this.objectCache.hasBySelfLinkObservable(item.self),
|
||||||
this.requestService.hasByHrefObservable(item.self)
|
this.requestService.hasByHrefObservable(item.uuid)
|
||||||
).pipe(
|
).pipe(
|
||||||
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
|
filter(([existsInOC, existsInRC]) => !existsInOC && !existsInRC),
|
||||||
take(1),
|
take(1),
|
||||||
@@ -367,7 +356,7 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
* @param nameVariant The name variant to set for the matching relationship
|
* @param nameVariant The name variant to set for the matching relationship
|
||||||
*/
|
*/
|
||||||
public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable<RemoteData<Relationship>> {
|
public updateNameVariant(item1: Item, item2: Item, relationshipLabel: string, nameVariant: string): Observable<RemoteData<Relationship>> {
|
||||||
return this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
|
const update$: Observable<RemoteData<Relationship>> = this.getRelationshipByItemsAndLabel(item1, item2, relationshipLabel)
|
||||||
.pipe(
|
.pipe(
|
||||||
switchMap((relation: Relationship) =>
|
switchMap((relation: Relationship) =>
|
||||||
relation.relationshipType.pipe(
|
relation.relationshipType.pipe(
|
||||||
@@ -388,14 +377,44 @@ export class RelationshipService extends DataService<Relationship> {
|
|||||||
}
|
}
|
||||||
return this.update(updatedRelationship);
|
return this.update(updatedRelationship);
|
||||||
}),
|
}),
|
||||||
// skipWhile((relationshipRD: RemoteData<Relationship>) => !relationshipRD.isSuccessful)
|
);
|
||||||
tap((relationshipRD: RemoteData<Relationship>) => {
|
|
||||||
if (relationshipRD.hasSucceeded) {
|
update$.pipe(
|
||||||
|
filter((relationshipRD: RemoteData<Relationship>) => relationshipRD.state === RemoteDataState.RequestPending),
|
||||||
|
take(1),
|
||||||
|
).subscribe(() => {
|
||||||
this.removeRelationshipItemsFromCache(item1);
|
this.removeRelationshipItemsFromCache(item1);
|
||||||
this.removeRelationshipItemsFromCache(item2);
|
this.removeRelationshipItemsFromCache(item2);
|
||||||
|
});
|
||||||
|
|
||||||
|
return update$
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
)
|
/**
|
||||||
|
* Method to update the the right or left place of a relationship
|
||||||
|
* The useLeftItem field in the reorderable relationship determines which place should be updated
|
||||||
|
* @param reoRel
|
||||||
|
*/
|
||||||
|
public updatePlace(reoRel: ReorderableRelationship): Observable<RemoteData<Relationship>> {
|
||||||
|
let updatedRelationship;
|
||||||
|
if (reoRel.useLeftItem) {
|
||||||
|
updatedRelationship = Object.assign(new Relationship(), reoRel.relationship, { rightPlace: reoRel.newIndex });
|
||||||
|
} else {
|
||||||
|
updatedRelationship = Object.assign(new Relationship(), reoRel.relationship, { leftPlace: reoRel.newIndex });
|
||||||
|
}
|
||||||
|
|
||||||
|
const update$ = this.update(updatedRelationship);
|
||||||
|
|
||||||
|
update$.pipe(
|
||||||
|
filter((relationshipRD: RemoteData<Relationship>) => relationshipRD.state === RemoteDataState.ResponsePending),
|
||||||
|
take(1),
|
||||||
|
).subscribe((relationshipRD: RemoteData<Relationship>) => {
|
||||||
|
if (relationshipRD.state === RemoteDataState.ResponsePending) {
|
||||||
|
this.removeRelationshipItemsFromCacheByRelationship(reoRel.relationship.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return update$;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -22,11 +22,9 @@ import { getSucceededRemoteData } from '../shared/operators';
|
|||||||
* Service responsible for handling requests related to the Site object
|
* Service responsible for handling requests related to the Site object
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SiteDataService extends DataService<Site> {
|
export class SiteDataService extends DataService<Site> {
|
||||||
|
|
||||||
protected linkPath = 'sites';
|
protected linkPath = 'sites';
|
||||||
protected forceBypassCache = false;
|
protected forceBypassCache = false;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
@@ -42,8 +40,6 @@ export class SiteDataService extends DataService<Site> {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the endpoint for browsing the site object
|
* Get the endpoint for browsing the site object
|
||||||
* @param {FindListOptions} options
|
* @param {FindListOptions} options
|
||||||
@@ -53,8 +49,6 @@ export class SiteDataService extends DataService<Site> {
|
|||||||
return this.halService.getEndpoint(this.linkPath);
|
return this.halService.getEndpoint(this.linkPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the Site Object
|
* Retrieve the Site Object
|
||||||
*/
|
*/
|
||||||
|
@@ -117,8 +117,7 @@ export class SearchService implements OnDestroy {
|
|||||||
* @param responseMsToLive The amount of milliseconds for the response to live in cache
|
* @param responseMsToLive The amount of milliseconds for the response to live in cache
|
||||||
* @returns {Observable<RequestEntry>} Emits an observable with the request entries
|
* @returns {Observable<RequestEntry>} Emits an observable with the request entries
|
||||||
*/
|
*/
|
||||||
searchEntries(searchOptions?: PaginatedSearchOptions, responseMsToLive?:number)
|
searchEntries(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number): Observable<{searchOptions: PaginatedSearchOptions, requestEntry: RequestEntry}> {
|
||||||
:Observable<{searchOptions: PaginatedSearchOptions, requestEntry: RequestEntry}> {
|
|
||||||
|
|
||||||
const hrefObs = this.getEndpoint(searchOptions);
|
const hrefObs = this.getEndpoint(searchOptions);
|
||||||
|
|
||||||
@@ -152,8 +151,7 @@ export class SearchService implements OnDestroy {
|
|||||||
* @param searchEntries: The request entries from the search method
|
* @param searchEntries: The request entries from the search method
|
||||||
* @returns {Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>} Emits a paginated list with all search results found
|
* @returns {Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>>} Emits a paginated list with all search results found
|
||||||
*/
|
*/
|
||||||
getPaginatedResults(searchEntries:Observable<{ searchOptions:PaginatedSearchOptions, requestEntry:RequestEntry }>)
|
getPaginatedResults(searchEntries: Observable<{ searchOptions: PaginatedSearchOptions, requestEntry: RequestEntry }>): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
||||||
:Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
|
||||||
const requestEntryObs: Observable<RequestEntry> = searchEntries.pipe(
|
const requestEntryObs: Observable<RequestEntry> = searchEntries.pipe(
|
||||||
map((entry) => entry.requestEntry),
|
map((entry) => entry.requestEntry),
|
||||||
);
|
);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { metadataRepresentationComponent } from '../../../../shared/metadata-representation/metadata-representation.decorator';
|
import { metadataRepresentationComponent } from '../../../../shared/metadata-representation/metadata-representation.decorator';
|
||||||
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
import { ItemMetadataRepresentationListElementComponent } from '../../../../shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
import { ItemMetadataRepresentationListElementComponent } from '../../../../shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
||||||
|
@@ -53,16 +53,15 @@
|
|||||||
|
|
||||||
|
|
||||||
<div *ngIf="hasRelationLookup" class="mt-3">
|
<div *ngIf="hasRelationLookup" class="mt-3">
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled" cdkDropList (cdkDropListDropped)="moveSelection($event)">
|
||||||
<li *ngFor="let value of ( selectedValues$ | async)">
|
<ds-existing-metadata-list-element cdkDrag
|
||||||
<button type="button" class="close float-left" aria-label="Close button"
|
*ngFor="let reorderable of reorderables; trackBy: trackReorderable"
|
||||||
(click)="removeSelection(value.selectedResult)">
|
[reoRel]="reorderable"
|
||||||
<span aria-hidden="true">×</span>
|
[submissionItem]="item"
|
||||||
</button>
|
[listId]="listId"
|
||||||
<span class="d-inline-block align-middle ml-1">
|
[metadataFields]="model.metadataFields"
|
||||||
<ds-metadata-representation-loader [mdRepresentation]="value.mdRep"></ds-metadata-representation-loader>
|
[relationshipOptions]="model.relationship">
|
||||||
</span>
|
</ds-existing-metadata-list-element>
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy, ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
ComponentFactoryResolver,
|
ComponentFactoryResolver,
|
||||||
ContentChildren,
|
ContentChildren,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
NgZone,
|
NgZone,
|
||||||
OnChanges, OnDestroy,
|
OnChanges,
|
||||||
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
QueryList,
|
QueryList,
|
||||||
@@ -49,7 +50,10 @@ import {
|
|||||||
DynamicNGBootstrapTimePickerComponent
|
DynamicNGBootstrapTimePickerComponent
|
||||||
} from '@ng-dynamic-forms/ui-ng-bootstrap';
|
} from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
import {
|
||||||
|
Reorderable,
|
||||||
|
ReorderableRelationship
|
||||||
|
} from './existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
|
|
||||||
import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model';
|
import { DYNAMIC_FORM_CONTROL_TYPE_TYPEAHEAD } from './models/typeahead/dynamic-typeahead.model';
|
||||||
import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
|
import { DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN } from './models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
|
||||||
@@ -71,9 +75,8 @@ 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, startWith, switchMap, find } from 'rxjs/operators';
|
||||||
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { SelectableListState } from '../../../object-list/selectable-list/selectable-list.reducer';
|
|
||||||
import { SearchResult } from '../../../search/search-result.model';
|
import { SearchResult } from '../../../search/search-result.model';
|
||||||
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
|
||||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@@ -82,23 +85,18 @@ import { SelectableListService } from '../../../object-list/selectable-list/sele
|
|||||||
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
|
import { DsDynamicDisabledComponent } from './models/disabled/dynamic-disabled.component';
|
||||||
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 {
|
import { getAllSucceededRemoteData, getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
getAllSucceededRemoteData,
|
|
||||||
getRemoteDataPayload,
|
|
||||||
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';
|
||||||
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 { Store } from '@ngrx/store';
|
||||||
import { AppState } from '../../../../app.reducer';
|
import { AppState } from '../../../../app.reducer';
|
||||||
import { SubmissionObjectDataService } from '../../../../core/submission/submission-object-data.service';
|
import { SubmissionObjectDataService } from '../../../../core/submission/submission-object-data.service';
|
||||||
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
|
import { SubmissionObject } from '../../../../core/submission/models/submission-object.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
|
||||||
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
export function dsDynamicFormControlMapFn(model: DynamicFormControlModel): Type<DynamicFormControl> | null {
|
||||||
switch (model.type) {
|
switch (model.type) {
|
||||||
@@ -182,16 +180,14 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
@Input() hasErrorMessaging = false;
|
@Input() hasErrorMessaging = false;
|
||||||
@Input() layout = null as DynamicFormLayout;
|
@Input() layout = null as DynamicFormLayout;
|
||||||
@Input() model: any;
|
@Input() model: any;
|
||||||
relationships$: Observable<Array<SearchResult<Item>>>;
|
reorderables$: Observable<ReorderableRelationship[]>;
|
||||||
|
reorderables: ReorderableRelationship[];
|
||||||
hasRelationLookup: boolean;
|
hasRelationLookup: boolean;
|
||||||
modalRef: NgbModalRef;
|
modalRef: NgbModalRef;
|
||||||
item: Item;
|
item: Item;
|
||||||
listId: string;
|
listId: string;
|
||||||
searchConfig: string;
|
searchConfig: string;
|
||||||
selectedValues$: Observable<Array<{
|
|
||||||
selectedResult: SearchResult<Item>,
|
|
||||||
mdRep: MetadataRepresentation
|
|
||||||
}>>;
|
|
||||||
/**
|
/**
|
||||||
* List of subscriptions to unsubscribe from
|
* List of subscriptions to unsubscribe from
|
||||||
*/
|
*/
|
||||||
@@ -224,7 +220,8 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
private relationshipService: RelationshipService,
|
private relationshipService: RelationshipService,
|
||||||
private zone: NgZone,
|
private zone: NgZone,
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
private submissionObjectService: SubmissionObjectDataService
|
private submissionObjectService: SubmissionObjectDataService,
|
||||||
|
private ref: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
|
|
||||||
super(componentFactoryResolver, layoutService, validationService, dynamicFormInstanceService);
|
super(componentFactoryResolver, layoutService, validationService, dynamicFormInstanceService);
|
||||||
@@ -235,44 +232,59 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.hasRelationLookup = hasValue(this.model.relationship);
|
this.hasRelationLookup = hasValue(this.model.relationship);
|
||||||
|
this.reorderables = [];
|
||||||
if (this.hasRelationLookup) {
|
if (this.hasRelationLookup) {
|
||||||
|
|
||||||
this.listId = 'list-' + this.model.relationship.relationshipType;
|
this.listId = 'list-' + this.model.relationship.relationshipType;
|
||||||
const item$ = this.submissionObjectService
|
const item$ = this.submissionObjectService
|
||||||
.findById(this.model.submissionId).pipe(
|
.findById(this.model.submissionId).pipe(
|
||||||
getAllSucceededRemoteData(),
|
getAllSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
|
switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable<RemoteData<Item>>)
|
||||||
|
.pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
this.subs.push(item$.subscribe((item) => this.item = item));
|
this.subs.push(item$.subscribe((item) => this.item = item));
|
||||||
|
this.reorderables$ = item$.pipe(
|
||||||
|
switchMap((item) => this.relationService.getItemRelationshipsByLabel(item, this.model.relationship.relationshipType)
|
||||||
|
.pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((relationshipList: PaginatedList<Relationship>) => relationshipList.page),
|
||||||
|
startWith([]),
|
||||||
|
switchMap((relationships: Relationship[]) =>
|
||||||
|
observableCombineLatest(
|
||||||
|
relationships.map((relationship: Relationship) =>
|
||||||
|
relationship.leftItem.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((leftItem: Item) => {
|
||||||
|
return new ReorderableRelationship(relationship, leftItem.uuid !== this.item.uuid)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
))),
|
||||||
|
map((relationships: ReorderableRelationship[]) =>
|
||||||
|
relationships
|
||||||
|
.sort((a: Reorderable, b: Reorderable) => {
|
||||||
|
return Math.sign(a.getPlace() - b.getPlace());
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.subs.push(this.reorderables$.subscribe((rs) => {
|
||||||
|
this.reorderables = rs;
|
||||||
|
this.ref.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
this.relationService.getRelatedItemsByLabel(this.item, this.model.relationship.relationshipType).pipe(
|
this.relationService.getRelatedItemsByLabel(this.item, this.model.relationship.relationshipType).pipe(
|
||||||
map((items: RemoteData<PaginatedList<Item>>) => items.payload.page.map((item) => Object.assign(new ItemSearchResult(), { indexableObject: item }))),
|
map((items: RemoteData<PaginatedList<Item>>) => items.payload.page.map((item) => Object.assign(new ItemSearchResult(), { indexableObject: item }))),
|
||||||
).subscribe((relatedItems: Array<SearchResult<Item>>) => this.selectableListService.select(this.listId, relatedItems));
|
).subscribe((relatedItems: Array<SearchResult<Item>>) => this.selectableListService.select(this.listId, relatedItems));
|
||||||
|
|
||||||
this.relationships$ = this.selectableListService.getSelectableList(this.listId).pipe(
|
|
||||||
map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []),
|
|
||||||
) as Observable<Array<SearchResult<Item>>>;
|
|
||||||
this.selectedValues$ =
|
|
||||||
observableCombineLatest(item$, this.relationships$).pipe(
|
|
||||||
map(([item, relatedItems]: [Item, Array<SearchResult<DSpaceObject>>]) => {
|
|
||||||
return relatedItems
|
|
||||||
.map((element: SearchResult<Item>) => {
|
|
||||||
const relationMD: MetadataValue = item.firstMetadata(this.model.relationship.metadataField, { value: element.indexableObject.uuid });
|
|
||||||
if (hasValue(relationMD)) {
|
|
||||||
const metadataRepresentationMD: MetadataValue = item.firstMetadata(this.model.metadataFields, { authority: relationMD.authority });
|
|
||||||
return {
|
|
||||||
selectedResult: element,
|
|
||||||
mdRep: Object.assign(
|
|
||||||
new ItemMetadataRepresentation(metadataRepresentationMD),
|
|
||||||
element.indexableObject
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}).filter(hasValue)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,12 +346,29 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to remove a selected relationship from the item
|
* Method to move a relationship inside the list of relationships
|
||||||
* @param object The second item in the relationship, the submitted item being the first
|
* This will update the view and update the right or left place field of the relationships in the list
|
||||||
|
* @param event
|
||||||
*/
|
*/
|
||||||
removeSelection(object: SearchResult<Item>) {
|
moveSelection(event: CdkDragDrop<Relationship>) {
|
||||||
this.selectableListService.deselectSingle(this.listId, object);
|
this.zone.runOutsideAngular(() => {
|
||||||
this.store.dispatch(new RemoveRelationshipAction(this.item, object.indexableObject, this.model.relationship.relationshipType))
|
moveItemInArray(this.reorderables, event.previousIndex, event.currentIndex);
|
||||||
|
const reorderables: Reorderable[] = this.reorderables.map((reo: Reorderable, index: number) => {
|
||||||
|
reo.oldIndex = reo.getPlace();
|
||||||
|
reo.newIndex = index;
|
||||||
|
return reo;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
observableCombineLatest(
|
||||||
|
reorderables.map((rel: ReorderableRelationship) => {
|
||||||
|
if (rel.oldIndex !== rel.newIndex) {
|
||||||
|
return this.relationshipService.updatePlace(rel);
|
||||||
|
} else {
|
||||||
|
return observableOf(undefined) as Observable<RemoteData<Relationship>>;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,4 +379,11 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
.filter((sub) => hasValue(sub))
|
.filter((sub) => hasValue(sub))
|
||||||
.forEach((sub) => sub.unsubscribe());
|
.forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent unnecessary rerendering so fields don't lose focus
|
||||||
|
*/
|
||||||
|
trackReorderable(index, reorderable: Reorderable) {
|
||||||
|
return hasValue(reorderable) ? reorderable.getId() : undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
<li *ngIf="metadataRepresentation">
|
||||||
|
<button type="button" class="close float-left" aria-label="Move button" cdkDragHandle>
|
||||||
|
<i aria-hidden="true" class="fas fa-arrows-alt fa-xs"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="close float-left" aria-label="Close button" (click)="removeSelection()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<span class="d-inline-block align-middle ml-1">
|
||||||
|
<ds-metadata-representation-loader [mdRepresentation]="metadataRepresentation"></ds-metadata-representation-loader>
|
||||||
|
</span>
|
||||||
|
</li>
|
@@ -0,0 +1,92 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ExistingMetadataListElementComponent, Reorderable, ReorderableRelationship } from './existing-metadata-list-element.component';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { select, Store } from '@ngrx/store';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../testing/utils';
|
||||||
|
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
||||||
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
|
|
||||||
|
describe('ExistingMetadataListElementComponent', () => {
|
||||||
|
let component: ExistingMetadataListElementComponent;
|
||||||
|
let fixture: ComponentFixture<ExistingMetadataListElementComponent>;
|
||||||
|
let selectionService;
|
||||||
|
let store;
|
||||||
|
let listID;
|
||||||
|
let submissionItem;
|
||||||
|
let relationship;
|
||||||
|
let reoRel;
|
||||||
|
let metadataFields;
|
||||||
|
let relationshipOptions;
|
||||||
|
let uuid1;
|
||||||
|
let uuid2;
|
||||||
|
let relatedItem;
|
||||||
|
let leftItemRD$;
|
||||||
|
let rightItemRD$;
|
||||||
|
let relatedSearchResult;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
uuid1 = '91ce578d-2e63-4093-8c73-3faafd716000';
|
||||||
|
uuid2 = '0e9dba1c-e1c3-4e05-a539-446f08ef57a7';
|
||||||
|
selectionService = jasmine.createSpyObj('selectionService', ['deselectSingle']);
|
||||||
|
store = jasmine.createSpyObj('store', ['dispatch']);
|
||||||
|
listID = '1234-listID';
|
||||||
|
submissionItem = Object.assign(new Item(), { uuid: uuid1 });
|
||||||
|
metadataFields = ['dc.contributor.author'];
|
||||||
|
relationshipOptions = Object.assign(new RelationshipOptions(), { relationshipType: 'isPublicationOfAuthor', filter: 'test.filter', searchConfiguration: 'personConfiguration', nameVariants: true })
|
||||||
|
relatedItem = Object.assign(new Item(), { uuid: uuid2 });
|
||||||
|
leftItemRD$ = createSuccessfulRemoteDataObject$(relatedItem);
|
||||||
|
rightItemRD$ = createSuccessfulRemoteDataObject$(submissionItem);
|
||||||
|
relatedSearchResult = Object.assign(new ItemSearchResult(), { indexableObject: relatedItem });
|
||||||
|
|
||||||
|
relationship = Object.assign(new Relationship(), { leftItem: leftItemRD$, rightItem: rightItemRD$ });
|
||||||
|
reoRel = new ReorderableRelationship(relationship, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ExistingMetadataListElementComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SelectableListService, useValue: selectionService },
|
||||||
|
{ provide: Store, useValue: store },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ExistingMetadataListElementComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
component.listId = listID;
|
||||||
|
component.submissionItem = submissionItem;
|
||||||
|
component.reoRel = reoRel;
|
||||||
|
component.metadataFields = metadataFields;
|
||||||
|
component.relationshipOptions = relationshipOptions;
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.ngOnChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('removeSelection', () => {
|
||||||
|
it('should deselect the object in the selectable list service', () => {
|
||||||
|
component.removeSelection();
|
||||||
|
expect(selectionService.deselectSingle).toHaveBeenCalledWith(listID, relatedSearchResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch a RemoveRelationshipAction', () => {
|
||||||
|
component.removeSelection();
|
||||||
|
const action = new RemoveRelationshipAction(submissionItem, relatedItem, relationshipOptions.relationshipType);
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(action);
|
||||||
|
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
@@ -0,0 +1,123 @@
|
|||||||
|
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { MetadataRepresentation } from '../../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../../empty.util';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { filter } from 'rxjs/operators';
|
||||||
|
import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||||
|
import { ItemMetadataRepresentation } from '../../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
|
import { RelationshipOptions } from '../../models/relationship-options.model';
|
||||||
|
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
|
||||||
|
import { SelectableListService } from '../../../../object-list/selectable-list/selectable-list.service';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '../../../../../app.reducer';
|
||||||
|
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
|
||||||
|
|
||||||
|
// tslint:disable:max-classes-per-file
|
||||||
|
/**
|
||||||
|
* Abstract class that defines objects that can be reordered
|
||||||
|
*/
|
||||||
|
export abstract class Reorderable {
|
||||||
|
constructor(public oldIndex?: number, public newIndex?: number) {
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract getId(): string;
|
||||||
|
|
||||||
|
abstract getPlace(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single relationship that can be reordered in a list of multiple relationships
|
||||||
|
*/
|
||||||
|
export class ReorderableRelationship extends Reorderable {
|
||||||
|
relationship: Relationship;
|
||||||
|
useLeftItem: boolean;
|
||||||
|
|
||||||
|
constructor(relationship: Relationship, useLeftItem: boolean, oldIndex?: number, newIndex?: number) {
|
||||||
|
super(oldIndex, newIndex);
|
||||||
|
this.relationship = relationship;
|
||||||
|
this.useLeftItem = useLeftItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
getId(): string {
|
||||||
|
return this.relationship.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlace(): number {
|
||||||
|
if (this.useLeftItem) {
|
||||||
|
return this.relationship.rightPlace
|
||||||
|
} else {
|
||||||
|
return this.relationship.leftPlace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single existing relationship value as metadata in submission
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-existing-metadata-list-element',
|
||||||
|
templateUrl: './existing-metadata-list-element.component.html',
|
||||||
|
styleUrls: ['./existing-metadata-list-element.component.scss']
|
||||||
|
})
|
||||||
|
export class ExistingMetadataListElementComponent implements OnChanges, OnDestroy {
|
||||||
|
@Input() listId: string;
|
||||||
|
@Input() submissionItem: Item;
|
||||||
|
@Input() reoRel: ReorderableRelationship;
|
||||||
|
@Input() metadataFields: string[];
|
||||||
|
@Input() relationshipOptions: RelationshipOptions;
|
||||||
|
metadataRepresentation: MetadataRepresentation;
|
||||||
|
relatedItem: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of subscriptions to unsubscribe from
|
||||||
|
*/
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private selectableListService: SelectableListService,
|
||||||
|
private store: Store<AppState>
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges() {
|
||||||
|
const item$ = this.reoRel.useLeftItem ?
|
||||||
|
this.reoRel.relationship.leftItem : this.reoRel.relationship.rightItem;
|
||||||
|
this.subs.push(item$.pipe(
|
||||||
|
getAllSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
filter((item: Item) => hasValue(item) && isNotEmpty(item.uuid))
|
||||||
|
).subscribe((item: Item) => {
|
||||||
|
this.relatedItem = item;
|
||||||
|
const relationMD: MetadataValue = this.submissionItem.firstMetadata(this.relationshipOptions.metadataField, { value: this.relatedItem.uuid });
|
||||||
|
if (hasValue(relationMD)) {
|
||||||
|
const metadataRepresentationMD: MetadataValue = this.submissionItem.firstMetadata(this.metadataFields, { authority: relationMD.authority });
|
||||||
|
this.metadataRepresentation = Object.assign(
|
||||||
|
new ItemMetadataRepresentation(metadataRepresentationMD),
|
||||||
|
this.relatedItem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the selected relationship from the list
|
||||||
|
*/
|
||||||
|
removeSelection() {
|
||||||
|
this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem }));
|
||||||
|
this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from all subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs
|
||||||
|
.filter((sub) => hasValue(sub))
|
||||||
|
.forEach((sub) => sub.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// tslint:enable:max-classes-per-file
|
@@ -135,7 +135,6 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
|
|||||||
this.externalSourcesRD$ = this.externalSourceService.findAll();
|
this.externalSourcesRD$ = this.externalSourceService.findAll();
|
||||||
|
|
||||||
this.setTotals();
|
this.setTotals();
|
||||||
// this.setExistingNameVariants();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
|
@@ -3,6 +3,7 @@ import { Actions, Effect, ofType } from '@ngrx/effects';
|
|||||||
import { debounceTime, map, mergeMap, take, tap } from 'rxjs/operators';
|
import { debounceTime, map, mergeMap, take, tap } from 'rxjs/operators';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../../core/data/relationship.service';
|
||||||
|
import { getSucceededRemoteData } from '../../../../../core/shared/operators';
|
||||||
import { AddRelationshipAction, RelationshipAction, RelationshipActionTypes, RemoveRelationshipAction, UpdateRelationshipAction } from './relationship.actions';
|
import { AddRelationshipAction, RelationshipAction, RelationshipActionTypes, RemoveRelationshipAction, UpdateRelationshipAction } from './relationship.actions';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { hasNoValue, hasValue, hasValueOperator } from '../../../../empty.util';
|
import { hasNoValue, hasValue, hasValueOperator } from '../../../../empty.util';
|
||||||
@@ -88,7 +89,7 @@ export class RelationshipEffects {
|
|||||||
this.nameVariantUpdates[identifier] = nameVariant;
|
this.nameVariantUpdates[identifier] = nameVariant;
|
||||||
} else {
|
} else {
|
||||||
this.relationshipService.updateNameVariant(item1, item2, relationshipType, nameVariant)
|
this.relationshipService.updateNameVariant(item1, item2, relationshipType, nameVariant)
|
||||||
.pipe()
|
.pipe(getSucceededRemoteData())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,7 @@ import { getSucceededRemoteData } from '../../../../../core/shared/operators';
|
|||||||
import { InputSuggestion } from '../../../../input-suggestions/input-suggestions.model';
|
import { InputSuggestion } from '../../../../input-suggestions/input-suggestions.model';
|
||||||
import { SearchOptions } from '../../../search-options.model';
|
import { SearchOptions } from '../../../search-options.model';
|
||||||
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 { currentPath } from '../../../../utils/route.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-facet-filter',
|
selector: 'ds-search-facet-filter',
|
||||||
@@ -185,7 +186,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public getSearchLink(): string {
|
public getSearchLink(): string {
|
||||||
if (this.inPlaceSearch) {
|
if (this.inPlaceSearch) {
|
||||||
return '';
|
return currentPath(this.router);
|
||||||
}
|
}
|
||||||
return this.searchService.getSearchLink();
|
return this.searchService.getSearchLink();
|
||||||
}
|
}
|
||||||
|
@@ -48,10 +48,7 @@ import { LogOutComponent } from './log-out/log-out.component';
|
|||||||
import { FormComponent } from './form/form.component';
|
import { FormComponent } from './form/form.component';
|
||||||
import { DsDynamicTypeaheadComponent } from './form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component';
|
import { DsDynamicTypeaheadComponent } from './form/builder/ds-dynamic-form-ui/models/typeahead/dynamic-typeahead.component';
|
||||||
import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
import { DsDynamicScrollableDropdownComponent } from './form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.component';
|
||||||
import {
|
import { DsDynamicFormControlContainerComponent, dsDynamicFormControlMapFn } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
||||||
DsDynamicFormControlContainerComponent,
|
|
||||||
dsDynamicFormControlMapFn
|
|
||||||
} from './form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component';
|
|
||||||
import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component';
|
import { DsDynamicFormComponent } from './form/builder/ds-dynamic-form-ui/ds-dynamic-form.component';
|
||||||
import { DYNAMIC_FORM_CONTROL_MAP_FN, DynamicFormsCoreModule } from '@ng-dynamic-forms/core';
|
import { DYNAMIC_FORM_CONTROL_MAP_FN, DynamicFormsCoreModule } from '@ng-dynamic-forms/core';
|
||||||
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstrap';
|
||||||
@@ -174,9 +171,10 @@ import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.component'
|
|||||||
import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component';
|
import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component';
|
||||||
import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component';
|
import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component';
|
||||||
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component';
|
||||||
import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component';
|
|
||||||
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
import { SelectableListItemControlComponent } from './object-collection/shared/selectable-list-item-control/selectable-list-item-control.component';
|
||||||
import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component';
|
import { DsDynamicLookupRelationExternalSourceTabComponent } from './form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component';
|
||||||
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
|
import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -199,6 +197,7 @@ const MODULES = [
|
|||||||
MomentModule,
|
MomentModule,
|
||||||
TextMaskModule,
|
TextMaskModule,
|
||||||
MenuModule,
|
MenuModule,
|
||||||
|
DragDropModule
|
||||||
];
|
];
|
||||||
|
|
||||||
const ROOT_MODULES = [
|
const ROOT_MODULES = [
|
||||||
@@ -335,7 +334,8 @@ const COMPONENTS = [
|
|||||||
ItemSelectComponent,
|
ItemSelectComponent,
|
||||||
CollectionSelectComponent,
|
CollectionSelectComponent,
|
||||||
MetadataRepresentationLoaderComponent,
|
MetadataRepresentationLoaderComponent,
|
||||||
SelectableListItemControlComponent
|
SelectableListItemControlComponent,
|
||||||
|
ExistingMetadataListElementComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -439,7 +439,8 @@ const DIRECTIVES = [
|
|||||||
...DIRECTIVES,
|
...DIRECTIVES,
|
||||||
...ENTRY_COMPONENTS,
|
...ENTRY_COMPONENTS,
|
||||||
...SHARED_ITEM_PAGE_COMPONENTS,
|
...SHARED_ITEM_PAGE_COMPONENTS,
|
||||||
PublicationSearchResultListElementComponent
|
PublicationSearchResultListElementComponent,
|
||||||
|
ExistingMetadataListElementComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...PROVIDERS
|
...PROVIDERS
|
||||||
|
@@ -70,6 +70,5 @@ describe('PageWithSidebarComponent', () => {
|
|||||||
it('should open the menu', () => {
|
it('should open the menu', () => {
|
||||||
expect(menu.classList).toContain('active');
|
expect(menu.classList).toContain('active');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,8 +1,28 @@
|
|||||||
import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
Input,
|
||||||
|
OnChanges,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
SimpleChanges
|
||||||
|
} from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, filter, map, mergeMap, reduce, startWith, flatMap, find } from 'rxjs/operators';
|
import {
|
||||||
|
debounceTime,
|
||||||
|
distinctUntilChanged,
|
||||||
|
filter,
|
||||||
|
find,
|
||||||
|
flatMap,
|
||||||
|
map,
|
||||||
|
mergeMap,
|
||||||
|
reduce,
|
||||||
|
startWith
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||||
@@ -227,8 +247,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
|
|||||||
} else {
|
} else {
|
||||||
return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5);
|
return listCollection.filter((v) => v.collection.name.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1).slice(0, 5);
|
||||||
}
|
}
|
||||||
})
|
}));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -113,6 +113,13 @@
|
|||||||
"parameter": "nospace",
|
"parameter": "nospace",
|
||||||
"property-declaration": "nospace",
|
"property-declaration": "nospace",
|
||||||
"variable-declaration": "nospace"
|
"variable-declaration": "nospace"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"call-signature": "onespace",
|
||||||
|
"index-signature": "onespace",
|
||||||
|
"parameter": "onespace",
|
||||||
|
"property-declaration": "onespace",
|
||||||
|
"variable-declaration": "onespace"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"unified-signatures": true,
|
"unified-signatures": true,
|
||||||
|
Reference in New Issue
Block a user