mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
[DURACOM-234] Migrate fo functional resolver
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -11,24 +12,20 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BitstreamFormatsResolver {
|
||||
constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an bitstreamFormat based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {RouterStateSnapshot} state
|
||||
* @param {BitstreamFormatDataService} bitstreamFormatDataService The BitstreamFormatDataService
|
||||
* @returns Observable<<RemoteData<BitstreamFormat>> Emits the found bitstreamFormat based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<BitstreamFormat>> {
|
||||
return this.bitstreamFormatDataService.findById(route.params.id)
|
||||
export const BitstreamFormatsResolver: ResolveFn<RemoteData<BitstreamFormat>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamFormatDataService: BitstreamFormatDataService = inject(BitstreamFormatDataService),
|
||||
): Observable<RemoteData<BitstreamFormat>> => {
|
||||
return bitstreamFormatDataService.findById(route.params.id)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -24,32 +25,20 @@ export const BITSTREAM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Bitstream>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific bitstream before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BitstreamPageResolver {
|
||||
constructor(private bitstreamService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a bitstream based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {BitstreamDataService} bitstreamService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Bitstream>> {
|
||||
return this.bitstreamService.findById(route.params.id, true, false, ...this.followLinks)
|
||||
export const BitstreamPageResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<RemoteData<Bitstream>> => {
|
||||
return bitstreamService.findById(route.params.id, true, false, ...BITSTREAM_PAGE_LINKS_TO_FOLLOW)
|
||||
.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Bitstream>[] {
|
||||
return BITSTREAM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,7 +7,7 @@ import { RequestEntryState } from '../core/data/request-entry-state.model';
|
||||
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||
|
||||
describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
let resolver: LegacyBitstreamUrlResolver;
|
||||
let resolver: any;
|
||||
let bitstreamDataService: BitstreamDataService;
|
||||
let testScheduler;
|
||||
let remoteDataMocks;
|
||||
@@ -33,7 +33,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
bitstreamDataService = {
|
||||
findByItemHandle: () => undefined,
|
||||
} as any;
|
||||
resolver = new LegacyBitstreamUrlResolver(bitstreamDataService);
|
||||
resolver = LegacyBitstreamUrlResolver;
|
||||
});
|
||||
|
||||
describe(`resolve`, () => {
|
||||
@@ -51,7 +51,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
route.params.sequence_id,
|
||||
@@ -78,7 +78,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle and filename from the route, and the sequence ID from the queryParams`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
route.queryParams.sequenceId,
|
||||
@@ -100,7 +100,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
});
|
||||
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
|
||||
testScheduler.run(() => {
|
||||
resolver.resolve(route, state);
|
||||
resolver(route, state, bitstreamDataService);
|
||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||
`${route.params.prefix}/${route.params.suffix}`,
|
||||
undefined,
|
||||
@@ -123,7 +123,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
c: remoteDataMocks.Error,
|
||||
};
|
||||
|
||||
expectObservable(resolver.resolve(route, state)).toBe(expected, values);
|
||||
expectObservable(resolver(route, state, bitstreamDataService)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
it(`...succeeded`, () => {
|
||||
@@ -138,7 +138,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
||||
c: remoteDataMocks.Success,
|
||||
};
|
||||
|
||||
expectObservable(resolver.resolve(route, state)).toBe(expected, values);
|
||||
expectObservable(resolver(route, state, bitstreamDataService)).toBe(expected, values);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -12,26 +13,20 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { hasNoValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This class resolves a bitstream based on the DSpace 6 XMLUI or JSPUI bitstream download URLs
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class LegacyBitstreamUrlResolver {
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a bitstream based on the handle of the item, and the sequence id or the filename of the
|
||||
* bitstream
|
||||
*
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {BitstreamDataService} bitstreamDataService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found bitstream based on the parameters in
|
||||
* current route, or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
|
||||
Observable<RemoteData<Bitstream>> {
|
||||
export const LegacyBitstreamUrlResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
bitstreamDataService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<RemoteData<Bitstream>> => {
|
||||
const prefix = route.params.prefix;
|
||||
const suffix = route.params.suffix;
|
||||
const filename = route.params.filename;
|
||||
@@ -41,12 +36,11 @@ export class LegacyBitstreamUrlResolver {
|
||||
sequenceId = route.queryParams.sequenceId;
|
||||
}
|
||||
|
||||
return this.bitstreamDataService.findByItemHandle(
|
||||
return bitstreamDataService.findByItemHandle(
|
||||
`${prefix}/${suffix}`,
|
||||
sequenceId,
|
||||
filename,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -19,30 +20,28 @@ import {
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject on a browse by page
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BrowseByDSOBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: DSpaceObjectDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {DSOBreadcrumbsService} breadcrumbService
|
||||
* @param {DSpaceObjectDataService} dataService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Community | Collection>> {
|
||||
export const BrowseByDSOBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community | Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: DSpaceObjectDataService = inject(DSpaceObjectDataService),
|
||||
): Observable<BreadcrumbConfig<Community | Collection>> => {
|
||||
const uuid = route.queryParams.scope;
|
||||
if (hasValue(uuid)) {
|
||||
return this.dataService.findById(uuid).pipe(
|
||||
return dataService.findById(uuid).pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: Community | Collection) => {
|
||||
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };
|
||||
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
|
||||
}),
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,32 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* This class resolves a BreadcrumbConfig object with an i18n key string for a route
|
||||
* It adds the metadata field of the current browse-by page
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BrowseByI18nBreadcrumbResolver extends I18nBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
||||
super(breadcrumbService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a browse-by i18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object for a browse-by page
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
export const BrowseByI18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): BreadcrumbConfig<string> => {
|
||||
const extendedBreadcrumbKey = route.data.breadcrumbKey + '.' + route.params.id;
|
||||
route.data = Object.assign({}, route.data, { breadcrumbKey: extendedBreadcrumbKey });
|
||||
return super.resolve(route, state);
|
||||
}
|
||||
}
|
||||
return I18nBreadcrumbResolver(route, state) as BreadcrumbConfig<string>;
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
@@ -5,7 +6,7 @@ import { CollectionPageResolver } from './collection-page.resolver';
|
||||
|
||||
describe('CollectionPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CollectionPageResolver;
|
||||
let resolver: any;
|
||||
let collectionService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
@@ -17,12 +18,11 @@ describe('CollectionPageResolver', () => {
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CollectionPageResolver(collectionService, store);
|
||||
resolver = CollectionPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a collection with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
.pipe(first())
|
||||
(resolver({ params: { id: uuid } } as any, { url: 'current-url' } as any, collectionService, store) as Observable<any>).pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
expect(resolved.payload.id).toEqual(uuid);
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
@@ -28,25 +30,21 @@ export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CollectionPageResolver {
|
||||
constructor(
|
||||
private collectionService: CollectionDataService,
|
||||
private store: Store<any>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a collection based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param collectionService
|
||||
* @param store
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
||||
const collectionRD$ = this.collectionService.findById(
|
||||
export const CollectionPageResolver: ResolveFn<RemoteData<Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
collectionService: CollectionDataService = inject(CollectionDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Collection>> => {
|
||||
const collectionRD$ = collectionService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
@@ -56,9 +54,8 @@ export class CollectionPageResolver {
|
||||
);
|
||||
|
||||
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||
store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||
});
|
||||
|
||||
return collectionRD$;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,27 +1,24 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { DSONameServiceMock } from '../../shared/mocks/dso-name.service.mock';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { ItemTemplatePageResolver } from './item-template-page.resolver';
|
||||
|
||||
describe('ItemTemplatePageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: ItemTemplatePageResolver;
|
||||
let resolver: any;
|
||||
let itemTemplateService: any;
|
||||
let dsoNameService: DSONameServiceMock;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
beforeEach(() => {
|
||||
itemTemplateService = {
|
||||
findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||
};
|
||||
dsoNameService = new DSONameServiceMock();
|
||||
resolver = new ItemTemplatePageResolver(dsoNameService as DSONameService, itemTemplateService);
|
||||
resolver = ItemTemplatePageResolver;
|
||||
});
|
||||
|
||||
it('should resolve an item template with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
(resolver({ params: { id: uuid } } as any, undefined, itemTemplateService) as Observable<any>)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,38 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection's item template before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemTemplatePageResolver {
|
||||
constructor(
|
||||
public dsoNameService: DSONameService,
|
||||
private itemTemplateService: ItemTemplateDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a collection's item template based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found item template based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||
export const ItemTemplatePageResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
itemTemplateService: ItemTemplateDataService = inject(ItemTemplateDataService),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { Observable } from 'rxjs';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
@@ -5,7 +6,7 @@ import { CommunityPageResolver } from './community-page.resolver';
|
||||
|
||||
describe('CommunityPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CommunityPageResolver;
|
||||
let resolver: any;
|
||||
let communityService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
@@ -17,11 +18,11 @@ describe('CommunityPageResolver', () => {
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CommunityPageResolver(communityService, store);
|
||||
resolver = CommunityPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a community with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
(resolver({ params: { id: uuid } } as any, { url: 'current-url' } as any, communityService, store) as Observable<any>)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
@@ -28,25 +30,21 @@ export const COMMUNITY_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Community>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific community before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CommunityPageResolver {
|
||||
constructor(
|
||||
private communityService: CommunityDataService,
|
||||
private store: Store<any>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a community based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {CommunityDataService} communityService
|
||||
* @param {Store} store
|
||||
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
||||
const communityRD$ = this.communityService.findById(
|
||||
export const CommunityPageResolver: ResolveFn<RemoteData<Community>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
communityService: CommunityDataService = inject(CommunityDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Community>> => {
|
||||
const communityRD$ = communityService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
@@ -56,9 +54,8 @@ export class CommunityPageResolver {
|
||||
);
|
||||
|
||||
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||
store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||
});
|
||||
|
||||
return communityRD$;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,31 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BITSTREAM_PAGE_LINKS_TO_FOLLOW } from '../../bitstream-page/bitstream-page.resolver';
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { BitstreamDataService } from '../data/bitstream-data.service';
|
||||
import { Bitstream } from '../shared/bitstream.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { BitstreamBreadcrumbsService } from './bitstream-breadcrumbs.service';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for an Item
|
||||
* The resolve function that resolves the BreadcrumbConfig object for an Item
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BitstreamBreadcrumbResolver extends DSOBreadcrumbResolver<Bitstream> {
|
||||
constructor(
|
||||
protected breadcrumbService: BitstreamBreadcrumbsService, protected dataService: BitstreamDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
export const BitstreamBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Bitstream>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: BitstreamBreadcrumbsService = inject(BitstreamBreadcrumbsService),
|
||||
dataService: BitstreamDataService = inject(BitstreamDataService),
|
||||
): Observable<BreadcrumbConfig<Bitstream>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = BITSTREAM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Bitstream>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Bitstream>[] {
|
||||
return BITSTREAM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,29 +1,36 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { COLLECTION_PAGE_LINKS_TO_FOLLOW } from '../../collection-page/collection-page.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { CollectionDataService } from '../data/collection-data.service';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Collection
|
||||
* The resolve function that resolves the BreadcrumbConfig object for a Collection
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CollectionDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
export const CollectionBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Collection>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: CollectionDataService = inject(CollectionDataService),
|
||||
): Observable<BreadcrumbConfig<Collection>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COLLECTION_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Collection>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Collection>[] {
|
||||
return COLLECTION_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
|
@@ -1,29 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { CommunityDataService } from '../data/community-data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Community
|
||||
* The resolve function that resolves the BreadcrumbConfig object for a Community
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CommunityDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Community>[] {
|
||||
return COMMUNITY_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
export const CommunityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: CommunityDataService = inject(CommunityDataService),
|
||||
): Observable<BreadcrumbConfig<Community>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Community>>;
|
||||
};
|
||||
|
@@ -3,11 +3,10 @@ import { getTestScheduler } from 'jasmine-marbles';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { CollectionBreadcrumbResolver } from './collection-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
|
||||
describe('DSOBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: DSOBreadcrumbResolver<Collection>;
|
||||
let resolver: any;
|
||||
let collectionService: any;
|
||||
let dsoBreadcrumbService: any;
|
||||
let testCollection: Collection;
|
||||
@@ -17,18 +16,18 @@ describe('DSOBreadcrumbResolver', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
uuid = '1234-65487-12354-1235';
|
||||
breadcrumbUrl = '/collections/' + uuid;
|
||||
currentUrl = breadcrumbUrl + '/edit';
|
||||
breadcrumbUrl = `/collections/${uuid}`;
|
||||
currentUrl = `${breadcrumbUrl}/edit`;
|
||||
testCollection = Object.assign(new Collection(), { uuid });
|
||||
dsoBreadcrumbService = {};
|
||||
collectionService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$(testCollection),
|
||||
findById: () => createSuccessfulRemoteDataObject$(testCollection),
|
||||
};
|
||||
resolver = new CollectionBreadcrumbResolver(dsoBreadcrumbService, collectionService);
|
||||
resolver = CollectionBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve a breadcrumb config for the correct DSO', () => {
|
||||
const resolvedConfig = resolver.resolve({ params: { id: uuid } } as any, { url: currentUrl } as any);
|
||||
const resolvedConfig = resolver({ params: { id: uuid } } as any, { url: currentUrl } as any, dsoBreadcrumbService, collectionService);
|
||||
const expectedConfig = { provider: dsoBreadcrumbService, key: testCollection, url: breadcrumbUrl };
|
||||
getTestScheduler().expectObservable(resolvedConfig).toBe('(a|)', { a: expectedConfig });
|
||||
});
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
@@ -10,7 +9,6 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||
import { ChildHALResource } from '../shared/child-hal-resource.model';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
@@ -19,45 +17,33 @@ import {
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> {
|
||||
protected constructor(
|
||||
protected breadcrumbService: DSOBreadcrumbsService,
|
||||
protected dataService: IdentifiableDataService<T>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a breadcrumb config object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {DSOBreadcrumbsService} breadcrumbService
|
||||
* @param {IdentifiableDataService} dataService
|
||||
* @param linksToFollow
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
|
||||
export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService,
|
||||
dataService: IdentifiableDataService<DSpaceObject>,
|
||||
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
|
||||
): Observable<BreadcrumbConfig<DSpaceObject>> => {
|
||||
const uuid = route.params.id;
|
||||
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
|
||||
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
map((object: T) => {
|
||||
map((object: DSpaceObject) => {
|
||||
if (hasValue(object)) {
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
|
||||
return { provider: this.breadcrumbService, key: object, url: url };
|
||||
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
|
||||
return { provider: breadcrumbService, key: object, url: url };
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
abstract get followLinks(): FollowLinkConfig<T>[];
|
||||
}
|
||||
};
|
||||
|
@@ -3,7 +3,7 @@ import { I18nBreadcrumbResolver } from './i18n-breadcrumb.resolver';
|
||||
|
||||
describe('I18nBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: I18nBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let i18nBreadcrumbService: any;
|
||||
let i18nKey: string;
|
||||
let route: any;
|
||||
@@ -27,18 +27,18 @@ describe('I18nBreadcrumbResolver', () => {
|
||||
};
|
||||
expectedPath = new URLCombiner(parentSegment, segment).toString();
|
||||
i18nBreadcrumbService = {};
|
||||
resolver = new I18nBreadcrumbResolver(i18nBreadcrumbService);
|
||||
resolver = I18nBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route, {} as any);
|
||||
const resolvedConfig = resolver(route, {} as any, i18nBreadcrumbService);
|
||||
const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: expectedPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
||||
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
||||
expect(() => {
|
||||
resolver.resolve({ data: {} } as any, undefined);
|
||||
resolver({ data: {} } as any, undefined, i18nBreadcrumbService);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -10,27 +11,21 @@ import { currentPathFromSnapshot } from '../../shared/utils/route.utils';
|
||||
import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class I18nBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {I18nBreadcrumbsService} breadcrumbService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
export const I18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: I18nBreadcrumbsService = inject(I18nBreadcrumbsService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const key = route.data.breadcrumbKey;
|
||||
if (hasNoValue(key)) {
|
||||
throw new Error('You provided an i18nBreadcrumbResolver for url \"' + route.url + '\" but no breadcrumbKey in the route\'s data');
|
||||
}
|
||||
const fullPath = currentPathFromSnapshot(route);
|
||||
return { provider: this.breadcrumbService, key: key, url: fullPath };
|
||||
}
|
||||
}
|
||||
return { provider: breadcrumbService, key: key, url: fullPath };
|
||||
};
|
||||
|
@@ -1,29 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page/item.resolver';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { ItemDataService } from '../data/item-data.service';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for an Item
|
||||
* The resolve function that resolves the BreadcrumbConfig object for an Item
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: ItemDataService) {
|
||||
super(breadcrumbService, dataService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that returns the follow links to already resolve
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Item>[] {
|
||||
return ITEM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
export const ItemBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||
dataService: ItemDataService = inject(ItemDataService),
|
||||
): Observable<BreadcrumbConfig<Item>> => {
|
||||
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = ITEM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||
return DSOBreadcrumbResolver(
|
||||
route,
|
||||
state,
|
||||
breadcrumbService,
|
||||
dataService,
|
||||
...linksToFollow,
|
||||
) as Observable<BreadcrumbConfig<Item>>;
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@ import { NavigationBreadcrumbResolver } from './navigation-breadcrumb.resolver';
|
||||
|
||||
describe('NavigationBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: NavigationBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let NavigationBreadcrumbService: any;
|
||||
let i18nKey: string;
|
||||
let relatedI18nKey: string;
|
||||
@@ -40,11 +40,11 @@ describe('NavigationBreadcrumbResolver', () => {
|
||||
};
|
||||
expectedPath = '/base/example:/base';
|
||||
NavigationBreadcrumbService = {};
|
||||
resolver = new NavigationBreadcrumbResolver(NavigationBreadcrumbService);
|
||||
resolver = NavigationBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route, state);
|
||||
const resolvedConfig = resolver(route, state, NavigationBreadcrumbService);
|
||||
const expectedConfig = { provider: NavigationBreadcrumbService, key: `${i18nKey}:${relatedI18nKey}`, url: expectedPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -8,37 +9,21 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
||||
import { NavigationBreadcrumbsService } from './navigation-breadcrumb.service';
|
||||
|
||||
/**
|
||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route and related parents
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class NavigationBreadcrumbResolver {
|
||||
|
||||
private parentRoutes: ActivatedRouteSnapshot[] = [];
|
||||
constructor(protected breadcrumbService: NavigationBreadcrumbsService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to collect all parent routes snapshot from current route snapshot
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
*/
|
||||
private getParentRoutes(route: ActivatedRouteSnapshot): void {
|
||||
if (route.parent) {
|
||||
this.parentRoutes.push(route.parent);
|
||||
this.getParentRoutes(route.parent);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Method for resolving an I18n breadcrumb configuration object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {NavigationBreadcrumbsService} breadcrumbService
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
this.getParentRoutes(route);
|
||||
export const NavigationBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: NavigationBreadcrumbsService = inject(NavigationBreadcrumbsService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const parentRoutes: ActivatedRouteSnapshot[] = [];
|
||||
getParentRoutes(route, parentRoutes);
|
||||
const relatedRoutes = route.data.relatedRoutes;
|
||||
const parentPaths = this.parentRoutes.map(parent => parent.routeConfig?.path);
|
||||
const parentPaths = parentRoutes.map(parent => parent.routeConfig?.path);
|
||||
const relatedParentRoutes = relatedRoutes.filter(relatedRoute => parentPaths.includes(relatedRoute.path));
|
||||
const baseUrlSegmentPath = route.parent.url[route.parent.url.length - 1].path;
|
||||
const baseUrl = state.url.substring(0, state.url.lastIndexOf(baseUrlSegmentPath) + baseUrlSegmentPath.length);
|
||||
@@ -51,6 +36,17 @@ export class NavigationBreadcrumbResolver {
|
||||
return `${previous}:${baseUrl}${current.path}`;
|
||||
}, state.url);
|
||||
|
||||
return { provider: this.breadcrumbService, key: combinedParentBreadcrumbKeys, url: combinedUrls };
|
||||
return { provider: breadcrumbService, key: combinedParentBreadcrumbKeys, url: combinedUrls };
|
||||
};
|
||||
|
||||
/**
|
||||
* Method to collect all parent routes snapshot from current route snapshot
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {ActivatedRouteSnapshot[]} parentRoutes
|
||||
*/
|
||||
function getParentRoutes(route: ActivatedRouteSnapshot, parentRoutes: ActivatedRouteSnapshot[]): void {
|
||||
if (route.parent) {
|
||||
parentRoutes.push(route.parent);
|
||||
getParentRoutes(route.parent, parentRoutes);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { PublicationClaimBreadcrumbResolver } from './publication-claim-breadcru
|
||||
|
||||
describe('PublicationClaimBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: PublicationClaimBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let publicationClaimBreadcrumbService: any;
|
||||
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||
@@ -19,11 +19,11 @@ describe('PublicationClaimBreadcrumbResolver', () => {
|
||||
},
|
||||
};
|
||||
publicationClaimBreadcrumbService = {};
|
||||
resolver = new PublicationClaimBreadcrumbResolver(publicationClaimBreadcrumbService);
|
||||
resolver = PublicationClaimBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route as any, { url: fullPath } as any);
|
||||
const resolvedConfig = resolver(route as any, { url: fullPath } as any, publicationClaimBreadcrumbService);
|
||||
const expectedConfig = { provider: publicationClaimBreadcrumbService, key: expectedKey };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,28 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PublicationClaimBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: PublicationClaimBreadcrumbService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that resolve Publication Claim item into a breadcrumb
|
||||
* The parameter are retrieved by the url since part of the Publication Claim route config
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
export const PublicationClaimBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: PublicationClaimBreadcrumbService = inject(PublicationClaimBreadcrumbService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||
return { provider: this.breadcrumbService, key: targetId };
|
||||
}
|
||||
}
|
||||
return { provider: breadcrumbService, key: targetId };
|
||||
};
|
||||
|
@@ -2,7 +2,7 @@ import { QualityAssuranceBreadcrumbResolver } from './quality-assurance-breadcru
|
||||
|
||||
describe('QualityAssuranceBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: QualityAssuranceBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let qualityAssuranceBreadcrumbService: any;
|
||||
let route: any;
|
||||
const fullPath = '/test/quality-assurance/';
|
||||
@@ -19,11 +19,11 @@ describe('QualityAssuranceBreadcrumbResolver', () => {
|
||||
},
|
||||
};
|
||||
qualityAssuranceBreadcrumbService = {};
|
||||
resolver = new QualityAssuranceBreadcrumbResolver(qualityAssuranceBreadcrumbService);
|
||||
resolver = QualityAssuranceBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', () => {
|
||||
const resolvedConfig = resolver.resolve(route as any, { url: fullPath + 'testSourceId' } as any);
|
||||
const resolvedConfig = resolver(route as any, { url: fullPath + 'testSourceId' } as any, qualityAssuranceBreadcrumbService);
|
||||
const expectedConfig = { provider: qualityAssuranceBreadcrumbService, key: expectedKey, url: fullPath };
|
||||
expect(resolvedConfig).toEqual(expectedConfig);
|
||||
});
|
||||
|
@@ -1,26 +1,18 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||
import { QualityAssuranceBreadcrumbService } from './quality-assurance-breadcrumb.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class QualityAssuranceBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: QualityAssuranceBreadcrumbService) {}
|
||||
|
||||
/**
|
||||
* Method that resolve QA item into a breadcrumb
|
||||
* The parameter are retrieved by the url since part of the QA route config
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns BreadcrumbConfig object
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
||||
export const QualityAssuranceBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: QualityAssuranceBreadcrumbService = inject(QualityAssuranceBreadcrumbService),
|
||||
): BreadcrumbConfig<string> => {
|
||||
const sourceId = route.paramMap.get('sourceId');
|
||||
const topicId = route.paramMap.get('topicId');
|
||||
let key = sourceId;
|
||||
@@ -29,8 +21,7 @@ export class QualityAssuranceBreadcrumbResolver {
|
||||
key += `:${topicId}`;
|
||||
}
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substr(0, fullPath.indexOf(sourceId));
|
||||
const url = fullPath.substring(0, fullPath.indexOf(sourceId));
|
||||
|
||||
return { provider: this.breadcrumbService, key, url };
|
||||
}
|
||||
}
|
||||
return { provider: breadcrumbService, key, url };
|
||||
};
|
||||
|
@@ -1,45 +1,37 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Item } from '../../shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../../shared/operators';
|
||||
import { SubmissionObject } from '../models/submission-object.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SubmissionObjectResolver<T> {
|
||||
constructor(
|
||||
protected dataService: IdentifiableDataService<any>,
|
||||
protected store: Store<any>,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {IdentifiableDataService<SubmissionObject> } dataService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<T>> {
|
||||
const itemRD$ = this.dataService.findById(route.params.id,
|
||||
export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, dataService: IdentifiableDataService<SubmissionObject>) => Observable<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
dataService: IdentifiableDataService<SubmissionObject>,
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return dataService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<T>>),
|
||||
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
return itemRD$;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -9,21 +10,10 @@ import { take } from 'rxjs/operators';
|
||||
import { SiteDataService } from '../core/data/site-data.service';
|
||||
import { Site } from '../core/shared/site.model';
|
||||
|
||||
/**
|
||||
* The class that resolve the Site object for a route
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HomePageResolver {
|
||||
constructor(private siteService: SiteDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a site object
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<Site> Emits the found Site object, or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Site> | Promise<Site> | Site {
|
||||
return this.siteService.find().pipe(take(1));
|
||||
}
|
||||
}
|
||||
export const HomePageResolver: ResolveFn<Site> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
siteService: SiteDataService = inject(SiteDataService),
|
||||
): Observable<Site> => {
|
||||
return siteService.find().pipe(take(1));
|
||||
};
|
||||
|
@@ -19,7 +19,7 @@ describe('ItemPageResolver', () => {
|
||||
});
|
||||
|
||||
describe('resolve', () => {
|
||||
let resolver: ItemPageResolver;
|
||||
let resolver: any;
|
||||
let itemService: any;
|
||||
let store: any;
|
||||
let router: any;
|
||||
@@ -42,15 +42,19 @@ describe('ItemPageResolver', () => {
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new ItemPageResolver(itemService, store, router);
|
||||
resolver = ItemPageResolver;
|
||||
});
|
||||
|
||||
it('should redirect to the correct route for the entity type', (done) => {
|
||||
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
||||
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: router.parseUrl(`/items/${uuid}`).toString() } as any)
|
||||
.pipe(first())
|
||||
resolver({ params: { id: uuid } } as any,
|
||||
{ url: router.parseUrl(`/items/${uuid}`).toString() } as any,
|
||||
router,
|
||||
itemService,
|
||||
store,
|
||||
).pipe(first())
|
||||
.subscribe(
|
||||
() => {
|
||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl(`/entities/${entityType}/${uuid}`).toString());
|
||||
@@ -63,8 +67,13 @@ describe('ItemPageResolver', () => {
|
||||
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
||||
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: router.parseUrl(`/entities/${entityType}/${uuid}`).toString() } as any)
|
||||
.pipe(first())
|
||||
resolver(
|
||||
{ params: { id: uuid } } as any,
|
||||
{ url: router.parseUrl(`/entities/${entityType}/${uuid}`).toString() } as any,
|
||||
router,
|
||||
itemService,
|
||||
store,
|
||||
).pipe(first())
|
||||
.subscribe(
|
||||
() => {
|
||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -8,36 +9,47 @@ import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { ItemResolver } from './item.resolver';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from './item.resolver';
|
||||
import { getItemPageRoute } from './item-page-routing-paths';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated and will redirect to the
|
||||
* entity page
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemPageResolver extends ItemResolver {
|
||||
constructor(
|
||||
protected itemService: ItemDataService,
|
||||
protected store: Store<any>,
|
||||
protected router: Router,
|
||||
) {
|
||||
super(itemService, store, router);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {Router} router
|
||||
* @param {ItemDataService} itemService
|
||||
* @param {Store<AppState>} store
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return super.resolve(route, state).pipe(
|
||||
export const ItemPageResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
router: Router = inject(Router),
|
||||
itemService: ItemDataService = inject(ItemDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
const itemRD$ = itemService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||
store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||
});
|
||||
|
||||
return itemRD$.pipe(
|
||||
map((rd: RemoteData<Item>) => {
|
||||
if (rd.hasSucceeded && hasValue(rd.payload)) {
|
||||
const thisRoute = state.url;
|
||||
@@ -46,16 +58,15 @@ export class ItemPageResolver extends ItemResolver {
|
||||
// or semicolons) and thisRoute has been encoded with that function. If we want to compare
|
||||
// it with itemRoute, we have to run itemRoute through Angular's version as well to ensure
|
||||
// the same characters are encoded the same way.
|
||||
const itemRoute = this.router.parseUrl(getItemPageRoute(rd.payload)).toString();
|
||||
const itemRoute = router.parseUrl(getItemPageRoute(rd.payload)).toString();
|
||||
|
||||
if (!thisRoute.startsWith(itemRoute)) {
|
||||
const itemId = rd.payload.uuid;
|
||||
const subRoute = thisRoute.substring(thisRoute.indexOf(itemId) + itemId.length, thisRoute.length);
|
||||
this.router.navigateByUrl(itemRoute + subRoute);
|
||||
router.navigateByUrl(itemRoute + subRoute);
|
||||
}
|
||||
}
|
||||
return rd;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../app.reducer';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
@@ -31,27 +32,14 @@ export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
||||
followLink('thumbnail'),
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemResolver {
|
||||
constructor(
|
||||
protected itemService: ItemDataService,
|
||||
protected store: Store<any>,
|
||||
protected router: Router,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
const itemRD$ = this.itemService.findById(route.params.id,
|
||||
export const ItemResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
itemService: ItemDataService = inject(ItemDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
const itemRD$ = itemService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
||||
@@ -60,9 +48,8 @@ export class ItemResolver {
|
||||
);
|
||||
|
||||
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||
store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||
});
|
||||
|
||||
return itemRD$;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
Router,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { AppState } from '../../app.reducer';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { VersionDataService } from '../../core/data/version-data.service';
|
||||
import { ResolvedAction } from '../../core/resolving/resolver.actions';
|
||||
@@ -26,26 +27,21 @@ export const VERSION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Version>[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific version before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class VersionResolver {
|
||||
constructor(
|
||||
protected versionService: VersionDataService,
|
||||
protected store: Store<any>,
|
||||
protected router: Router,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a version based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {VersionDataService} versionService
|
||||
* @param {Store<AppState>} store
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Version>> {
|
||||
const versionRD$ = this.versionService.findById(route.params.id,
|
||||
export const VersionResolver: ResolveFn<RemoteData<Version>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
versionService: VersionDataService = inject(VersionDataService),
|
||||
store: Store<AppState> = inject(Store<AppState>),
|
||||
): Observable<RemoteData<Version>> => {
|
||||
const versionRD$ = versionService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
...VERSION_PAGE_LINKS_TO_FOLLOW,
|
||||
@@ -54,9 +50,8 @@ export class VersionResolver {
|
||||
);
|
||||
|
||||
versionRD$.subscribe((versionRD: RemoteData<Version>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, versionRD.payload));
|
||||
store.dispatch(new ResolvedAction(state.url, versionRD.payload));
|
||||
});
|
||||
|
||||
return versionRD$;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
846
src/app/menu-resolver.service.ts
Normal file
846
src/app/menu-resolver.service.ts
Normal file
@@ -0,0 +1,846 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
filter,
|
||||
find,
|
||||
map,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { PUBLICATION_CLAIMS_PATH } from './admin/admin-notifications/admin-notifications-routing-paths';
|
||||
import { BrowseService } from './core/browse/browse.service';
|
||||
import { ConfigurationDataService } from './core/data/configuration-data.service';
|
||||
import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||
import { PaginatedList } from './core/data/paginated-list.model';
|
||||
import {
|
||||
METADATA_EXPORT_SCRIPT_NAME,
|
||||
METADATA_IMPORT_SCRIPT_NAME,
|
||||
ScriptDataService,
|
||||
} from './core/data/processes/script-data.service';
|
||||
import { RemoteData } from './core/data/remote-data';
|
||||
import { BrowseDefinition } from './core/shared/browse-definition.model';
|
||||
import { ConfigurationProperty } from './core/shared/configuration-property.model';
|
||||
import { getFirstCompletedRemoteData } from './core/shared/operators';
|
||||
import { ThemedCreateCollectionParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component';
|
||||
import { ThemedCreateCommunityParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component';
|
||||
import { ThemedCreateItemParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component';
|
||||
import { ThemedEditCollectionSelectorComponent } from './shared/dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component';
|
||||
import { ThemedEditCommunitySelectorComponent } from './shared/dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component';
|
||||
import { ThemedEditItemSelectorComponent } from './shared/dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component';
|
||||
import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component';
|
||||
import { ExportMetadataSelectorComponent } from './shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { MenuID } from './shared/menu/menu-id.model';
|
||||
import { LinkMenuItemModel } from './shared/menu/menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from './shared/menu/menu-item/models/onclick.model';
|
||||
import { TextMenuItemModel } from './shared/menu/menu-item/models/text.model';
|
||||
import { MenuItemType } from './shared/menu/menu-item-type.model';
|
||||
import { MenuState } from './shared/menu/menu-state.model';
|
||||
|
||||
/**
|
||||
* Creates all of the app's menus
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MenuResolverService {
|
||||
constructor(
|
||||
protected menuService: MenuService,
|
||||
protected browseService: BrowseService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected configurationDataService: ConfigurationDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
return combineLatest([
|
||||
this.createPublicMenu$(),
|
||||
this.createAdminMenu$(),
|
||||
]).pipe(
|
||||
map((menusDone: boolean[]) => menusDone.every(Boolean)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a specific menu to appear
|
||||
* @param id the ID of the menu to wait for
|
||||
* @return an Observable that emits true as soon as the menu is created
|
||||
*/
|
||||
protected waitForMenu$(id: MenuID): Observable<boolean> {
|
||||
return this.menuService.getMenu(id).pipe(
|
||||
find((menu: MenuState) => hasValue(menu)),
|
||||
map(() => true),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.PUBLIC}
|
||||
*/
|
||||
createPublicMenu$(): Observable<boolean> {
|
||||
const menuList: any[] = [
|
||||
/* Communities & Collections tree */
|
||||
{
|
||||
id: `browse_global_communities_and_collections`,
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 0,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_communities_and_collections`,
|
||||
link: `/community-list`,
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
];
|
||||
// Read the different Browse-By types from config and add them to the browse menu
|
||||
this.browseService.getBrowseDefinitions()
|
||||
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
|
||||
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||
if (browseDefListRD.hasSucceeded) {
|
||||
browseDefListRD.payload.page.forEach((browseDef: BrowseDefinition) => {
|
||||
menuList.push({
|
||||
id: `browse_global_by_${browseDef.id}`,
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_by_${browseDef.id}`,
|
||||
link: `/browse/${browseDef.id}`,
|
||||
} as LinkMenuItemModel,
|
||||
});
|
||||
});
|
||||
menuList.push(
|
||||
/* Browse */
|
||||
{
|
||||
id: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 1,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.browse_global',
|
||||
} as TextMenuItemModel,
|
||||
},
|
||||
);
|
||||
}
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
|
||||
return this.waitForMenu$(MenuID.PUBLIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.ADMIN}
|
||||
*/
|
||||
createAdminMenu$() {
|
||||
this.createMainMenuSections();
|
||||
this.createSiteAdministratorMenuSections();
|
||||
this.createExportMenuSections();
|
||||
this.createImportMenuSections();
|
||||
this.createAccessControlMenuSections();
|
||||
this.createReportMenuSections();
|
||||
|
||||
return this.waitForMenu$(MenuID.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the main menu sections.
|
||||
* edit_community / edit_collection is only included if the current user is a Community or Collection admin
|
||||
*/
|
||||
createMainMenuSections() {
|
||||
combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSeeQA),
|
||||
this.authorizationService.isAuthorized(FeatureID.CoarNotifyEnabled),
|
||||
]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem, canSeeQa, isCoarNotifyEnabled]) => {
|
||||
const newSubMenuList = [
|
||||
{
|
||||
id: 'new_community',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCommunityParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_collection',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCollectionParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_item',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: canSubmit,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateItemParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_process',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.new_process',
|
||||
link: '/processes/new',
|
||||
} as LinkMenuItemModel,
|
||||
},/* ldn_services */
|
||||
{
|
||||
id: 'ldn_services_new',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.services_new',
|
||||
link: '/admin/ldn/services/new',
|
||||
} as LinkMenuItemModel,
|
||||
icon: '',
|
||||
},
|
||||
];
|
||||
const editSubMenuList = [
|
||||
/* Edit */
|
||||
{
|
||||
id: 'edit_community',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCommunitySelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_collection',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCollectionAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCollectionSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_item',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditItemSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
];
|
||||
const newSubMenu = {
|
||||
id: 'new',
|
||||
active: false,
|
||||
visible: newSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.new',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'plus',
|
||||
index: 0,
|
||||
};
|
||||
const editSubMenu = {
|
||||
id: 'edit',
|
||||
active: false,
|
||||
visible: editSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.edit',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 1,
|
||||
};
|
||||
|
||||
const menuList = [
|
||||
...newSubMenuList,
|
||||
newSubMenu,
|
||||
...editSubMenuList,
|
||||
editSubMenu,
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'new_item_version',
|
||||
// parentID: 'new',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.new_item_version',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
|
||||
/* Statistics */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'statistics_task',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.statistics_task',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'chart-bar',
|
||||
// index: 8
|
||||
// },
|
||||
|
||||
/* Control Panel */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'control_panel',
|
||||
// active: false,
|
||||
// visible: isSiteAdmin,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.control_panel',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'cogs',
|
||||
// index: 9
|
||||
// },
|
||||
|
||||
/* Processes */
|
||||
{
|
||||
id: 'processes',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.processes',
|
||||
link: '/processes',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'terminal',
|
||||
index: 10,
|
||||
},
|
||||
/* COAR Notify section */
|
||||
{
|
||||
id: 'coar_notify',
|
||||
active: false,
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.coar_notify',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'inbox',
|
||||
index: 13,
|
||||
},
|
||||
{
|
||||
id: 'notify_dashboard',
|
||||
active: false,
|
||||
parentID: 'coar_notify',
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.notify_dashboard',
|
||||
link: '/admin/notify-dashboard',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* LDN Services */
|
||||
{
|
||||
id: 'ldn_services',
|
||||
active: false,
|
||||
parentID: 'coar_notify',
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.services',
|
||||
link: '/admin/ldn/services',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'health',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.health',
|
||||
link: '/health',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'heartbeat',
|
||||
index: 11,
|
||||
},
|
||||
/* Notifications */
|
||||
{
|
||||
id: 'notifications',
|
||||
active: false,
|
||||
visible: canSeeQa || isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.notifications',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'bell',
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
id: 'notifications_quality-assurance',
|
||||
parentID: 'notifications',
|
||||
active: false,
|
||||
visible: canSeeQa,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.quality-assurance',
|
||||
link: '/notifications/quality-assurance',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'notifications_publication-claim',
|
||||
parentID: 'notifications',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.notifications_publication-claim',
|
||||
link: '/admin/notifications/' + PUBLICATION_CLAIMS_PATH,
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* Admin Search */
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the export scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createExportMenuSections() {
|
||||
const menuList = [
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_community',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_community',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_collection',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_collection',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_item',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_item',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists),
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
// Hides the export menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-export',
|
||||
index: 3,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_metadata',
|
||||
parentID: 'export',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_metadata',
|
||||
function: () => {
|
||||
this.modalService.open(ExportMetadataSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_batch',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_batch',
|
||||
function: () => {
|
||||
this.modalService.open(ExportBatchSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the import scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createImportMenuSections() {
|
||||
const menuList = [];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists),
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
// Hides the import menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.import',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-import',
|
||||
index: 2,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_metadata',
|
||||
parentID: 'import',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_metadata',
|
||||
link: '/admin/metadata-import',
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_batch',
|
||||
parentID: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_batch',
|
||||
link: '/admin/batch-import',
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
createSiteAdministratorMenuSections() {
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf)
|
||||
.subscribe((authorized) => {
|
||||
const menuList = [
|
||||
{
|
||||
id: 'admin_search',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.admin_search',
|
||||
link: '/admin/search',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'search',
|
||||
index: 5,
|
||||
},
|
||||
/* Registries */
|
||||
{
|
||||
id: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.registries',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'list',
|
||||
index: 6,
|
||||
},
|
||||
{
|
||||
id: 'registries_metadata',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_metadata',
|
||||
link: 'admin/registries/metadata',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'registries_format',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_format',
|
||||
link: 'admin/registries/bitstream-formats',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
|
||||
/* Curation tasks */
|
||||
{
|
||||
id: 'curation_tasks',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.curation_task',
|
||||
link: 'admin/curation-tasks',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'filter',
|
||||
index: 7,
|
||||
},
|
||||
|
||||
/* Workflow */
|
||||
{
|
||||
id: 'workflow',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.workflow',
|
||||
link: '/admin/workflow',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
index: 11,
|
||||
},
|
||||
{
|
||||
id: 'system_wide_alert',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.system-wide-alert',
|
||||
link: '/admin/system-wide-alert',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'exclamation-circle',
|
||||
index: 12,
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user can manage access control groups
|
||||
*/
|
||||
createAccessControlMenuSections() {
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanManageGroups),
|
||||
]).subscribe(([isSiteAdmin, canManageGroups]) => {
|
||||
const menuList = [
|
||||
/* Access Control */
|
||||
{
|
||||
id: 'access_control_people',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_people',
|
||||
link: '/access-control/epeople',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_groups',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_groups',
|
||||
link: '/access-control/groups',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_bulk',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_bulk',
|
||||
link: '/access-control/bulk-access',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'access_control_authorizations',
|
||||
// parentID: 'access_control',
|
||||
// active: false,
|
||||
// visible: authorized,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.access_control_authorizations',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
{
|
||||
id: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups || isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.access_control',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'key',
|
||||
index: 4,
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
createReportMenuSections() {
|
||||
observableCombineLatest([
|
||||
this.configurationDataService.findByPropertyName('contentreport.enable').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((res: RemoteData<ConfigurationProperty>) => res.hasSucceeded && res.payload && res.payload.values[0] === 'true'),
|
||||
),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).subscribe(([isSiteAdmin]) => {
|
||||
const menuList = [
|
||||
{
|
||||
id: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.reports',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-alt',
|
||||
index: 5,
|
||||
},
|
||||
/* Collections Report */
|
||||
{
|
||||
id: 'reports_collections',
|
||||
parentID: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.reports.collections',
|
||||
link: '/admin/reports/collections',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
},
|
||||
/* Queries Report */
|
||||
{
|
||||
id: 'reports_queries',
|
||||
parentID: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.reports.queries',
|
||||
link: '/admin/reports/queries',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
}
|
@@ -19,7 +19,6 @@ import { ConfigurationDataService } from './core/data/configuration-data.service
|
||||
import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||
import { ScriptDataService } from './core/data/processes/script-data.service';
|
||||
import { MenuResolver } from './menu.resolver';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { MenuID } from './shared/menu/menu-id.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from './shared/remote-data.utils';
|
||||
@@ -27,6 +26,7 @@ import { ConfigurationDataServiceStub } from './shared/testing/configuration-dat
|
||||
import { MenuServiceStub } from './shared/testing/menu-service.stub';
|
||||
import { createPaginatedList } from './shared/testing/utils.test';
|
||||
import createSpy = jasmine.createSpy;
|
||||
import { MenuResolverService } from './menu-resolver.service';
|
||||
|
||||
const BOOLEAN = { t: true, f: false };
|
||||
const MENU_STATE = {
|
||||
@@ -39,7 +39,7 @@ const BROWSE_DEFINITIONS = [
|
||||
];
|
||||
|
||||
describe('MenuResolver', () => {
|
||||
let resolver: MenuResolver;
|
||||
let resolver: MenuResolverService;
|
||||
|
||||
let menuService;
|
||||
let browseService;
|
||||
@@ -79,12 +79,12 @@ describe('MenuResolver', () => {
|
||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: ConfigurationDataService, useValue: configurationDataService },
|
||||
{
|
||||
provide: NgbModal, useValue: mockNgbModal },
|
||||
{ provide: NgbModal, useValue: mockNgbModal },
|
||||
MenuResolverService,
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
resolver = TestBed.inject(MenuResolver);
|
||||
resolver = TestBed.inject(MenuResolverService);
|
||||
}));
|
||||
|
||||
it('should be created', () => {
|
||||
|
@@ -1,846 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import {
|
||||
combineLatest,
|
||||
combineLatest as observableCombineLatest,
|
||||
Observable,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
filter,
|
||||
find,
|
||||
map,
|
||||
take,
|
||||
} from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { MenuResolverService } from './menu-resolver.service';
|
||||
|
||||
import { PUBLICATION_CLAIMS_PATH } from './admin/admin-notifications/admin-notifications-routing-paths';
|
||||
import { BrowseService } from './core/browse/browse.service';
|
||||
import { ConfigurationDataService } from './core/data/configuration-data.service';
|
||||
import { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||
import { PaginatedList } from './core/data/paginated-list.model';
|
||||
import {
|
||||
METADATA_EXPORT_SCRIPT_NAME,
|
||||
METADATA_IMPORT_SCRIPT_NAME,
|
||||
ScriptDataService,
|
||||
} from './core/data/processes/script-data.service';
|
||||
import { RemoteData } from './core/data/remote-data';
|
||||
import { BrowseDefinition } from './core/shared/browse-definition.model';
|
||||
import { ConfigurationProperty } from './core/shared/configuration-property.model';
|
||||
import { getFirstCompletedRemoteData } from './core/shared/operators';
|
||||
import { ThemedCreateCollectionParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-collection-parent-selector/themed-create-collection-parent-selector.component';
|
||||
import { ThemedCreateCommunityParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-community-parent-selector/themed-create-community-parent-selector.component';
|
||||
import { ThemedCreateItemParentSelectorComponent } from './shared/dso-selector/modal-wrappers/create-item-parent-selector/themed-create-item-parent-selector.component';
|
||||
import { ThemedEditCollectionSelectorComponent } from './shared/dso-selector/modal-wrappers/edit-collection-selector/themed-edit-collection-selector.component';
|
||||
import { ThemedEditCommunitySelectorComponent } from './shared/dso-selector/modal-wrappers/edit-community-selector/themed-edit-community-selector.component';
|
||||
import { ThemedEditItemSelectorComponent } from './shared/dso-selector/modal-wrappers/edit-item-selector/themed-edit-item-selector.component';
|
||||
import { ExportBatchSelectorComponent } from './shared/dso-selector/modal-wrappers/export-batch-selector/export-batch-selector.component';
|
||||
import { ExportMetadataSelectorComponent } from './shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { MenuID } from './shared/menu/menu-id.model';
|
||||
import { LinkMenuItemModel } from './shared/menu/menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from './shared/menu/menu-item/models/onclick.model';
|
||||
import { TextMenuItemModel } from './shared/menu/menu-item/models/text.model';
|
||||
import { MenuItemType } from './shared/menu/menu-item-type.model';
|
||||
import { MenuState } from './shared/menu/menu-state.model';
|
||||
|
||||
/**
|
||||
* Creates all of the app's menus
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class MenuResolver {
|
||||
constructor(
|
||||
protected menuService: MenuService,
|
||||
protected browseService: BrowseService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected scriptDataService: ScriptDataService,
|
||||
protected configurationDataService: ConfigurationDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
|
||||
return combineLatest([
|
||||
this.createPublicMenu$(),
|
||||
this.createAdminMenu$(),
|
||||
]).pipe(
|
||||
map((menusDone: boolean[]) => menusDone.every(Boolean)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a specific menu to appear
|
||||
* @param id the ID of the menu to wait for
|
||||
* @return an Observable that emits true as soon as the menu is created
|
||||
*/
|
||||
protected waitForMenu$(id: MenuID): Observable<boolean> {
|
||||
return this.menuService.getMenu(id).pipe(
|
||||
find((menu: MenuState) => hasValue(menu)),
|
||||
map(() => true),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.PUBLIC}
|
||||
*/
|
||||
createPublicMenu$(): Observable<boolean> {
|
||||
const menuList: any[] = [
|
||||
/* Communities & Collections tree */
|
||||
{
|
||||
id: `browse_global_communities_and_collections`,
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 0,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_communities_and_collections`,
|
||||
link: `/community-list`,
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
];
|
||||
// Read the different Browse-By types from config and add them to the browse menu
|
||||
this.browseService.getBrowseDefinitions()
|
||||
.pipe(getFirstCompletedRemoteData<PaginatedList<BrowseDefinition>>())
|
||||
.subscribe((browseDefListRD: RemoteData<PaginatedList<BrowseDefinition>>) => {
|
||||
if (browseDefListRD.hasSucceeded) {
|
||||
browseDefListRD.payload.page.forEach((browseDef: BrowseDefinition) => {
|
||||
menuList.push({
|
||||
id: `browse_global_by_${browseDef.id}`,
|
||||
parentID: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: `menu.section.browse_global_by_${browseDef.id}`,
|
||||
link: `/browse/${browseDef.id}`,
|
||||
} as LinkMenuItemModel,
|
||||
});
|
||||
});
|
||||
menuList.push(
|
||||
/* Browse */
|
||||
{
|
||||
id: 'browse_global',
|
||||
active: false,
|
||||
visible: true,
|
||||
index: 1,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.browse_global',
|
||||
} as TextMenuItemModel,
|
||||
},
|
||||
);
|
||||
}
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
|
||||
return this.waitForMenu$(MenuID.PUBLIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all menu sections and items for {@link MenuID.ADMIN}
|
||||
*/
|
||||
createAdminMenu$() {
|
||||
this.createMainMenuSections();
|
||||
this.createSiteAdministratorMenuSections();
|
||||
this.createExportMenuSections();
|
||||
this.createImportMenuSections();
|
||||
this.createAccessControlMenuSections();
|
||||
this.createReportMenuSections();
|
||||
|
||||
return this.waitForMenu$(MenuID.ADMIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the main menu sections.
|
||||
* edit_community / edit_collection is only included if the current user is a Community or Collection admin
|
||||
*/
|
||||
createMainMenuSections() {
|
||||
combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubmit),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditItem),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSeeQA),
|
||||
this.authorizationService.isAuthorized(FeatureID.CoarNotifyEnabled),
|
||||
]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin, canSubmit, canEditItem, canSeeQa, isCoarNotifyEnabled]) => {
|
||||
const newSubMenuList = [
|
||||
{
|
||||
id: 'new_community',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCommunityParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_collection',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateCollectionParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_item',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: canSubmit,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedCreateItemParentSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_process',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.new_process',
|
||||
link: '/processes/new',
|
||||
} as LinkMenuItemModel,
|
||||
},/* ldn_services */
|
||||
{
|
||||
id: 'ldn_services_new',
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.services_new',
|
||||
link: '/admin/ldn/services/new',
|
||||
} as LinkMenuItemModel,
|
||||
icon: '',
|
||||
},
|
||||
];
|
||||
const editSubMenuList = [
|
||||
/* Edit */
|
||||
{
|
||||
id: 'edit_community',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCommunityAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_community',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCommunitySelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_collection',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: isCollectionAdmin,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_collection',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditCollectionSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'edit_item',
|
||||
parentID: 'edit',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.edit_item',
|
||||
function: () => {
|
||||
this.modalService.open(ThemedEditItemSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
},
|
||||
];
|
||||
const newSubMenu = {
|
||||
id: 'new',
|
||||
active: false,
|
||||
visible: newSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.new',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'plus',
|
||||
index: 0,
|
||||
};
|
||||
const editSubMenu = {
|
||||
id: 'edit',
|
||||
active: false,
|
||||
visible: editSubMenuList.some(subMenu => subMenu.visible),
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.edit',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 1,
|
||||
};
|
||||
|
||||
const menuList = [
|
||||
...newSubMenuList,
|
||||
newSubMenu,
|
||||
...editSubMenuList,
|
||||
editSubMenu,
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'new_item_version',
|
||||
// parentID: 'new',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.new_item_version',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
|
||||
/* Statistics */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'statistics_task',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.statistics_task',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'chart-bar',
|
||||
// index: 8
|
||||
// },
|
||||
|
||||
/* Control Panel */
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'control_panel',
|
||||
// active: false,
|
||||
// visible: isSiteAdmin,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.control_panel',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// icon: 'cogs',
|
||||
// index: 9
|
||||
// },
|
||||
|
||||
/* Processes */
|
||||
{
|
||||
id: 'processes',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.processes',
|
||||
link: '/processes',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'terminal',
|
||||
index: 10,
|
||||
},
|
||||
/* COAR Notify section */
|
||||
{
|
||||
id: 'coar_notify',
|
||||
active: false,
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.coar_notify',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'inbox',
|
||||
index: 13,
|
||||
},
|
||||
{
|
||||
id: 'notify_dashboard',
|
||||
active: false,
|
||||
parentID: 'coar_notify',
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.notify_dashboard',
|
||||
link: '/admin/notify-dashboard',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* LDN Services */
|
||||
{
|
||||
id: 'ldn_services',
|
||||
active: false,
|
||||
parentID: 'coar_notify',
|
||||
visible: isSiteAdmin && isCoarNotifyEnabled,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.services',
|
||||
link: '/admin/ldn/services',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'health',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.health',
|
||||
link: '/health',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'heartbeat',
|
||||
index: 11,
|
||||
},
|
||||
/* Notifications */
|
||||
{
|
||||
id: 'notifications',
|
||||
active: false,
|
||||
visible: canSeeQa || isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.notifications',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'bell',
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
id: 'notifications_quality-assurance',
|
||||
parentID: 'notifications',
|
||||
active: false,
|
||||
visible: canSeeQa,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.quality-assurance',
|
||||
link: '/notifications/quality-assurance',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'notifications_publication-claim',
|
||||
parentID: 'notifications',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.notifications_publication-claim',
|
||||
link: '/admin/notifications/' + PUBLICATION_CLAIMS_PATH,
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
/* Admin Search */
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the export scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createExportMenuSections() {
|
||||
const menuList = [
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_community',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_community',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_collection',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_collection',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'export_item',
|
||||
// parentID: 'export',
|
||||
// active: false,
|
||||
// visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.export_item',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// shouldPersistOnRouteChange: true
|
||||
// },
|
||||
];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_EXPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
filter(([authorized, metadataExportScriptExists]: boolean[]) => authorized && metadataExportScriptExists),
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
// Hides the export menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.export',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-export',
|
||||
index: 3,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_metadata',
|
||||
parentID: 'export',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_metadata',
|
||||
function: () => {
|
||||
this.modalService.open(ExportMetadataSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'export_batch',
|
||||
parentID: 'export',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.export_batch',
|
||||
function: () => {
|
||||
this.modalService.open(ExportBatchSelectorComponent);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator and on whether or not
|
||||
* the import scripts exist and the current user is allowed to execute them
|
||||
*/
|
||||
createImportMenuSections() {
|
||||
const menuList = [];
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, menuSection));
|
||||
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.scriptDataService.scriptWithNameExistsAndCanExecute(METADATA_IMPORT_SCRIPT_NAME),
|
||||
]).pipe(
|
||||
filter(([authorized, metadataImportScriptExists]: boolean[]) => authorized && metadataImportScriptExists),
|
||||
take(1),
|
||||
).subscribe(() => {
|
||||
// Hides the import menu for unauthorised people
|
||||
// If in the future more sub-menus are added,
|
||||
// it should be reviewed if they need to be in this subscribe
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.import',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-import',
|
||||
index: 2,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_metadata',
|
||||
parentID: 'import',
|
||||
active: true,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_metadata',
|
||||
link: '/admin/metadata-import',
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
this.menuService.addSection(MenuID.ADMIN, {
|
||||
id: 'import_batch',
|
||||
parentID: 'import',
|
||||
active: false,
|
||||
visible: true,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.import_batch',
|
||||
link: '/admin/batch-import',
|
||||
} as LinkMenuItemModel,
|
||||
shouldPersistOnRouteChange: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
createSiteAdministratorMenuSections() {
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf)
|
||||
.subscribe((authorized) => {
|
||||
const menuList = [
|
||||
{
|
||||
id: 'admin_search',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.admin_search',
|
||||
link: '/admin/search',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'search',
|
||||
index: 5,
|
||||
},
|
||||
/* Registries */
|
||||
{
|
||||
id: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.registries',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'list',
|
||||
index: 6,
|
||||
},
|
||||
{
|
||||
id: 'registries_metadata',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_metadata',
|
||||
link: 'admin/registries/metadata',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'registries_format',
|
||||
parentID: 'registries',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.registries_format',
|
||||
link: 'admin/registries/bitstream-formats',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
|
||||
/* Curation tasks */
|
||||
{
|
||||
id: 'curation_tasks',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.curation_task',
|
||||
link: 'admin/curation-tasks',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'filter',
|
||||
index: 7,
|
||||
},
|
||||
|
||||
/* Workflow */
|
||||
{
|
||||
id: 'workflow',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.workflow',
|
||||
link: '/admin/workflow',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
index: 11,
|
||||
},
|
||||
{
|
||||
id: 'system_wide_alert',
|
||||
active: false,
|
||||
visible: authorized,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.system-wide-alert',
|
||||
link: '/admin/system-wide-alert',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'exclamation-circle',
|
||||
index: 12,
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user can manage access control groups
|
||||
*/
|
||||
createAccessControlMenuSections() {
|
||||
observableCombineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanManageGroups),
|
||||
]).subscribe(([isSiteAdmin, canManageGroups]) => {
|
||||
const menuList = [
|
||||
/* Access Control */
|
||||
{
|
||||
id: 'access_control_people',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_people',
|
||||
link: '/access-control/epeople',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_groups',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_groups',
|
||||
link: '/access-control/groups',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'access_control_bulk',
|
||||
parentID: 'access_control',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.access_control_bulk',
|
||||
link: '/access-control/bulk-access',
|
||||
} as LinkMenuItemModel,
|
||||
},
|
||||
// TODO: enable this menu item once the feature has been implemented
|
||||
// {
|
||||
// id: 'access_control_authorizations',
|
||||
// parentID: 'access_control',
|
||||
// active: false,
|
||||
// visible: authorized,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.access_control_authorizations',
|
||||
// link: ''
|
||||
// } as LinkMenuItemModel,
|
||||
// },
|
||||
{
|
||||
id: 'access_control',
|
||||
active: false,
|
||||
visible: canManageGroups || isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.access_control',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'key',
|
||||
index: 4,
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create menu sections dependent on whether or not the current user is a site administrator
|
||||
*/
|
||||
createReportMenuSections() {
|
||||
observableCombineLatest([
|
||||
this.configurationDataService.findByPropertyName('contentreport.enable').pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((res: RemoteData<ConfigurationProperty>) => res.hasSucceeded && res.payload && res.payload.values[0] === 'true'),
|
||||
),
|
||||
this.authorizationService.isAuthorized(FeatureID.AdministratorOf),
|
||||
]).subscribe(([isSiteAdmin]) => {
|
||||
const menuList = [
|
||||
{
|
||||
id: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.TEXT,
|
||||
text: 'menu.section.reports',
|
||||
} as TextMenuItemModel,
|
||||
icon: 'file-alt',
|
||||
index: 5,
|
||||
},
|
||||
/* Collections Report */
|
||||
{
|
||||
id: 'reports_collections',
|
||||
parentID: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.reports.collections',
|
||||
link: '/admin/reports/collections',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
},
|
||||
/* Queries Report */
|
||||
{
|
||||
id: 'reports_queries',
|
||||
parentID: 'reports',
|
||||
active: false,
|
||||
visible: isSiteAdmin,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'menu.section.reports.queries',
|
||||
link: '/admin/reports/queries',
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'user-check',
|
||||
},
|
||||
];
|
||||
|
||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.ADMIN, Object.assign(menuSection, {
|
||||
shouldPersistOnRouteChange: true,
|
||||
})));
|
||||
});
|
||||
}
|
||||
}
|
||||
export const MenuResolver: ResolveFn<boolean> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
menuResolverService: MenuResolverService = inject(MenuResolverService),
|
||||
): Observable<boolean> => {
|
||||
return menuResolverService.resolve(route, state);
|
||||
};
|
||||
|
@@ -5,7 +5,7 @@ import { Process } from './processes/process.model';
|
||||
|
||||
describe('ProcessBreadcrumbResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: ProcessBreadcrumbResolver;
|
||||
let resolver: any;
|
||||
let processDataService: ProcessDataService;
|
||||
let processBreadcrumbService: any;
|
||||
let process: Process;
|
||||
@@ -19,11 +19,16 @@ describe('ProcessBreadcrumbResolver', () => {
|
||||
processDataService = {
|
||||
findById: () => createSuccessfulRemoteDataObject$(process),
|
||||
} as any;
|
||||
resolver = new ProcessBreadcrumbResolver(processBreadcrumbService, processDataService);
|
||||
resolver = ProcessBreadcrumbResolver;
|
||||
});
|
||||
|
||||
it('should resolve the breadcrumb config', (done) => {
|
||||
const resolvedConfig = resolver.resolve({ data: { breadcrumbKey: process }, params: { id: id } } as any, { url: path } as any);
|
||||
const resolvedConfig = resolver(
|
||||
{ data: { breadcrumbKey: process }, params: { id: id } } as any,
|
||||
{ url: path } as any,
|
||||
processBreadcrumbService,
|
||||
processDataService,
|
||||
);
|
||||
const expectedConfig = { provider: processBreadcrumbService, key: process, url: path };
|
||||
resolvedConfig.subscribe((config) => {
|
||||
expect(config).toEqual(expectedConfig);
|
||||
@@ -33,7 +38,7 @@ describe('ProcessBreadcrumbResolver', () => {
|
||||
|
||||
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
||||
expect(() => {
|
||||
resolver.resolve({ data: {} } as any, undefined);
|
||||
resolver({ data: {} } as any, undefined, processBreadcrumbService, processDataService);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -15,30 +16,28 @@ import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
|
||||
import { Process } from './processes/process.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific process before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProcessBreadcrumbResolver {
|
||||
constructor(protected breadcrumbService: ProcessBreadcrumbsService, private processService: ProcessDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a process based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param breadcrumbService
|
||||
* @param processService
|
||||
* @returns Observable<<RemoteData<Process>> Emits the found process based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Process>> {
|
||||
export const ProcessBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Process>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
breadcrumbService: ProcessBreadcrumbsService = inject(ProcessBreadcrumbsService),
|
||||
processService: ProcessDataService = inject(ProcessDataService),
|
||||
): Observable<BreadcrumbConfig<Process>> => {
|
||||
const id = route.params.id;
|
||||
|
||||
return this.processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
||||
return processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((object: RemoteData<Process>) => {
|
||||
const fullPath = state.url;
|
||||
const url = fullPath.substr(0, fullPath.indexOf(id)) + id;
|
||||
return { provider: this.breadcrumbService, key: object.payload, url: url };
|
||||
const url = fullPath.substring(0, fullPath.indexOf(id)).concat(id);
|
||||
return { provider: breadcrumbService, key: object.payload, url: url };
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -16,23 +17,19 @@ export const PROCESS_PAGE_FOLLOW_LINKS = [
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific process before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ProcessPageResolver {
|
||||
constructor(private processService: ProcessDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a process based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {ProcessDataService} processService
|
||||
* @returns Observable<<RemoteData<Process>> Emits the found process based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
||||
return this.processService.findById(route.params.id, false, true, ...PROCESS_PAGE_FOLLOW_LINKS).pipe(
|
||||
export const ProcessPageResolver: ResolveFn<RemoteData<Process>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
processService: ProcessDataService = inject(ProcessDataService),
|
||||
): Observable<RemoteData<Process>> => {
|
||||
return processService.findById(route.params.id, false, true, ...PROCESS_PAGE_FOLLOW_LINKS).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
|
||||
@@ -14,22 +14,18 @@ export interface AssuranceEventsPageParams {
|
||||
}
|
||||
|
||||
/**
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class QualityAssuranceEventsPageResolver {
|
||||
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns AdminQualityAssuranceEventsPageParams Emits the route parameters
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): AssuranceEventsPageParams {
|
||||
export const ItemResolver: ResolveFn<AssuranceEventsPageParams> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): AssuranceEventsPageParams => {
|
||||
return {
|
||||
pageId: route.queryParams.pageId,
|
||||
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||
currentPage: parseInt(route.queryParams.page, 10),
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,53 +1,53 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
} from '../../../config/app-config.interface';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { QualityAssuranceSourceObject } from '../../core/notifications/qa/models/quality-assurance-source.model';
|
||||
import { QualityAssuranceSourceService } from '../../notifications/qa/source/quality-assurance-source.service';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that retrieve the route data before the route is activated.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SourceDataResolver {
|
||||
private pageSize = environment.qualityAssuranceConfig.pageSize;
|
||||
/**
|
||||
* Initialize the effect class variables.
|
||||
* @param {QualityAssuranceSourceService} qualityAssuranceSourceService
|
||||
*/
|
||||
constructor(
|
||||
private qualityAssuranceSourceService: QualityAssuranceSourceService,
|
||||
private router: Router,
|
||||
) { }
|
||||
/**
|
||||
* Method for resolving the parameters in the current route.
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param router
|
||||
* @param qualityAssuranceSourceService
|
||||
* @param appConfig
|
||||
* @returns Observable<QualityAssuranceSourceObject[]>
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<QualityAssuranceSourceObject[]> {
|
||||
return this.qualityAssuranceSourceService.getSources(this.pageSize, 0).pipe(
|
||||
export const SourceDataResolver: ResolveFn<QualityAssuranceSourceObject[]> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
router: Router = inject(Router),
|
||||
qualityAssuranceSourceService: QualityAssuranceSourceService = inject(QualityAssuranceSourceService),
|
||||
appConfig: AppConfig = inject(APP_CONFIG),
|
||||
): Observable<QualityAssuranceSourceObject[]> => {
|
||||
const pageSize = appConfig.qualityAssuranceConfig.pageSize;
|
||||
|
||||
return qualityAssuranceSourceService.getSources(pageSize, 0).pipe(
|
||||
map((sources: PaginatedList<QualityAssuranceSourceObject>) => {
|
||||
if (sources.page.length === 1) {
|
||||
this.router.navigate([this.getResolvedUrl(route) + '/' + sources.page[0].id]);
|
||||
router.navigate([getResolvedUrl(route) + '/' + sources.page[0].id]);
|
||||
}
|
||||
return sources.page;
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
*
|
||||
* @param route url path
|
||||
* @returns url path
|
||||
*/
|
||||
getResolvedUrl(route: ActivatedRouteSnapshot): string {
|
||||
function getResolvedUrl(route: ActivatedRouteSnapshot): string {
|
||||
return route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
|
||||
}
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||
import { RegistrationResolver } from './registration.resolver';
|
||||
|
||||
describe('RegistrationResolver', () => {
|
||||
let resolver: RegistrationResolver;
|
||||
let resolver: any;
|
||||
let epersonRegistrationService: EpersonRegistrationService;
|
||||
|
||||
const token = 'test-token';
|
||||
@@ -16,11 +16,11 @@ describe('RegistrationResolver', () => {
|
||||
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
||||
searchByToken: createSuccessfulRemoteDataObject$(registration),
|
||||
});
|
||||
resolver = new RegistrationResolver(epersonRegistrationService);
|
||||
resolver = RegistrationResolver;
|
||||
});
|
||||
describe('resolve', () => {
|
||||
it('should resolve a registration based on the token', (done) => {
|
||||
resolver.resolve({ params: { token: token } } as any, undefined)
|
||||
resolver({ params: { token: token } } as any, undefined, epersonRegistrationService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -10,19 +11,13 @@ import { RemoteData } from '../core/data/remote-data';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { Registration } from '../core/shared/registration.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
/**
|
||||
* Resolver to resolve a Registration object based on the provided token
|
||||
*/
|
||||
export class RegistrationResolver {
|
||||
|
||||
constructor(private epersonRegistrationService: EpersonRegistrationService) {
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Registration>> {
|
||||
export const RegistrationResolver: ResolveFn<RemoteData<Registration>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
epersonRegistrationService: EpersonRegistrationService = inject(EpersonRegistrationService),
|
||||
): Observable<RemoteData<Registration>> => {
|
||||
const token = route.params.token;
|
||||
return this.epersonRegistrationService.searchByToken(token).pipe(
|
||||
return epersonRegistrationService.searchByToken(token).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -10,21 +11,12 @@ import { RemoteData } from '../core/data/remote-data';
|
||||
import { ItemRequest } from '../core/shared/item-request.model';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
|
||||
/**
|
||||
* Resolves an {@link ItemRequest} from the token found in the route's parameters
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RequestCopyResolver {
|
||||
|
||||
constructor(
|
||||
private itemRequestDataService: ItemRequestDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<ItemRequest>> | Promise<RemoteData<ItemRequest>> | RemoteData<ItemRequest> {
|
||||
return this.itemRequestDataService.findById(route.params.token).pipe(
|
||||
export const RequestCopyResolver: ResolveFn<RemoteData<ItemRequest>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
itemRequestDataService: ItemRequestDataService = inject(ItemRequestDataService),
|
||||
): Observable<RemoteData<ItemRequest>> => {
|
||||
return itemRequestDataService.findById(route.params.token).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
324
src/app/shared/dso-page/dso-edit-menu-resolver.service.ts
Normal file
324
src/app/shared/dso-page/dso-edit-menu-resolver.service.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
combineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
map,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
|
||||
import { getDSORoute } from '../../app-routing-paths';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
getRemoteDataPayload,
|
||||
} from '../../core/shared/operators';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import {
|
||||
hasNoValue,
|
||||
hasValue,
|
||||
isNotEmpty,
|
||||
} from '../empty.util';
|
||||
import { MenuService } from '../menu/menu.service';
|
||||
import { MenuID } from '../menu/menu-id.model';
|
||||
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||
import { MenuSection } from '../menu/menu-section.model';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { SubscriptionModalComponent } from '../subscriptions/subscription-modal/subscription-modal.component';
|
||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import {
|
||||
DsoWithdrawnReinstateModalService,
|
||||
REQUEST_REINSTATE,
|
||||
REQUEST_WITHDRAWN,
|
||||
} from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
|
||||
/**
|
||||
* Creates the menus for the dspace object pages
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DSOEditMenuResolverService {
|
||||
|
||||
constructor(
|
||||
protected dSpaceObjectDataService: DSpaceObjectDataService,
|
||||
protected menuService: MenuService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected dsoVersioningModalService: DsoVersioningModalService,
|
||||
protected researcherProfileService: ResearcherProfileDataService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService,
|
||||
protected dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService,
|
||||
private correctionTypeDataService: CorrectionTypeDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise all dspace object related menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{ [key: string]: MenuSection[] }> {
|
||||
let id = route.params.id;
|
||||
if (hasNoValue(id) && hasValue(route.queryParams.scope)) {
|
||||
id = route.queryParams.scope;
|
||||
}
|
||||
if (hasNoValue(id)) {
|
||||
// If there's no ID, we're not on a DSO homepage, so pass on any pre-existing menu route data
|
||||
return observableOf({ ...route.data?.menu });
|
||||
} else {
|
||||
return this.dSpaceObjectDataService.findById(id, true, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((dsoRD) => {
|
||||
if (dsoRD.hasSucceeded) {
|
||||
const dso = dsoRD.payload;
|
||||
return combineLatest(this.getDsoMenus(dso, route, state)).pipe(
|
||||
// Menu sections are retrieved as an array of arrays and flattened into a single array
|
||||
map((combinedMenus) => [].concat.apply([], combinedMenus)),
|
||||
map((menus) => this.addDsoUuidToMenuIDs(menus, dso)),
|
||||
map((menus) => {
|
||||
return {
|
||||
...route.data?.menu,
|
||||
[MenuID.DSO_EDIT]: menus,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf({ ...route.data?.menu });
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the menus for a dso based on the route and state
|
||||
*/
|
||||
getDsoMenus(dso, route, state): Observable<MenuSection[]>[] {
|
||||
return [
|
||||
this.getItemMenu(dso),
|
||||
this.getComColMenu(dso),
|
||||
this.getCommonMenu(dso, state),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common menus between all dspace objects
|
||||
*/
|
||||
protected getCommonMenu(dso, state): Observable<MenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
|
||||
]).pipe(
|
||||
map(([canEditItem]) => {
|
||||
return [
|
||||
{
|
||||
id: 'edit-dso',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: this.getDsoType(dso) + '.page.edit',
|
||||
link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString(),
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 2,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item specific menus
|
||||
*/
|
||||
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Item) {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||
this.correctionTypeDataService.findByItem(dso.uuid, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload()),
|
||||
]).pipe(
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem, correction]) => {
|
||||
const isPerson = this.getDsoType(dso) === 'person';
|
||||
return [
|
||||
{
|
||||
id: 'orcid-dso',
|
||||
active: false,
|
||||
visible: isPerson && canSynchronizeWithOrcid,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'item.page.orcid.tooltip',
|
||||
link: new URLCombiner(getDSORoute(dso), 'orcid').toString(),
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'orcid fab fa-lg',
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
id: 'version-dso',
|
||||
active: false,
|
||||
visible: canCreateVersion,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: versionTooltip,
|
||||
disabled: disableVersioning,
|
||||
function: () => {
|
||||
this.dsoVersioningModalService.openCreateVersionModal(dso);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'code-branch',
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
id: 'claim-dso',
|
||||
active: false,
|
||||
visible: isPerson && canClaimItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'item.page.claim.button',
|
||||
function: () => {
|
||||
this.claimResearcher(dso);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'hand-paper',
|
||||
index: 3,
|
||||
},
|
||||
{
|
||||
id: 'withdrawn-item',
|
||||
active: false,
|
||||
visible: dso.isArchived && correction?.page.some((c) => c.topic === REQUEST_WITHDRAWN),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.withdrawn',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-withdrawn', dso.isArchived);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye-slash',
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
id: 'reinstate-item',
|
||||
active: false,
|
||||
visible: dso.isWithdrawn && correction?.page.some((c) => c.topic === REQUEST_REINSTATE),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.reinstate',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-reinstate', dso.isArchived);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye',
|
||||
index: 5,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Community/Collection-specific menus
|
||||
*/
|
||||
protected getComColMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Community || dso instanceof Collection) {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubscribe, dso.self),
|
||||
]).pipe(
|
||||
map(([canSubscribe]) => {
|
||||
return [
|
||||
{
|
||||
id: 'subscribe',
|
||||
active: false,
|
||||
visible: canSubscribe,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'subscriptions.tooltip',
|
||||
function: () => {
|
||||
const modalRef = this.modalService.open(SubscriptionModalComponent);
|
||||
modalRef.componentInstance.dso = dso;
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'bell',
|
||||
index: 4,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a researcher by creating a profile
|
||||
* Shows notifications and/or hides the menu section on success/error
|
||||
*/
|
||||
protected claimResearcher(dso) {
|
||||
this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(dso.self)
|
||||
.subscribe((id: string) => {
|
||||
if (isNotEmpty(id)) {
|
||||
this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'),
|
||||
this.translate.get('researcherprofile.success.claim.body'));
|
||||
this.authorizationService.invalidateAuthorizationsRequestCache();
|
||||
this.menuService.hideMenuSection(MenuID.DSO_EDIT, 'claim-dso-' + dso.uuid);
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translate.get('researcherprofile.error.claim.title'),
|
||||
this.translate.get('researcherprofile.error.claim.body'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dso or entity type for an object to be used in generic messages
|
||||
*/
|
||||
protected getDsoType(dso) {
|
||||
const renderType = dso.getRenderTypes()[0];
|
||||
if (typeof renderType === 'string' || renderType instanceof String) {
|
||||
return renderType.toLowerCase();
|
||||
} else {
|
||||
return dso.type.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dso uuid to all provided menu ids and parent ids
|
||||
*/
|
||||
protected addDsoUuidToMenuIDs(menus, dso) {
|
||||
return menus.map((menu) => {
|
||||
Object.assign(menu, {
|
||||
id: menu.id + '-' + dso.uuid,
|
||||
});
|
||||
if (hasValue(menu.parentID)) {
|
||||
Object.assign(menu, {
|
||||
parentID: menu.parentID + '-' + dso.uuid,
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -40,7 +40,7 @@ import {
|
||||
} from '../remote-data.utils';
|
||||
import { MenuServiceStub } from '../testing/menu-service.stub';
|
||||
import { createPaginatedList } from '../testing/utils.test';
|
||||
import { DSOEditMenuResolver } from './dso-edit-menu.resolver';
|
||||
import { DSOEditMenuResolverService } from './dso-edit-menu-resolver.service';
|
||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import { DsoWithdrawnReinstateModalService } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('DSOEditMenuResolver', () => {
|
||||
id: 'some menu',
|
||||
};
|
||||
|
||||
let resolver: DSOEditMenuResolver;
|
||||
let resolver: DSOEditMenuResolverService;
|
||||
|
||||
let dSpaceObjectDataService;
|
||||
let menuService;
|
||||
@@ -189,12 +189,12 @@ describe('DSOEditMenuResolver', () => {
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
||||
{ provide: CorrectionTypeDataService, useValue: correctionsDataService },
|
||||
{
|
||||
provide: NgbModal, useValue: mockNgbModal },
|
||||
{ provide: NgbModal, useValue: mockNgbModal },
|
||||
DSOEditMenuResolverService,
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
});
|
||||
resolver = TestBed.inject(DSOEditMenuResolver);
|
||||
resolver = TestBed.inject(DSOEditMenuResolverService);
|
||||
|
||||
spyOn(menuService, 'addSection');
|
||||
}));
|
||||
|
@@ -1,324 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
combineLatest,
|
||||
Observable,
|
||||
of as observableOf,
|
||||
} from 'rxjs';
|
||||
import {
|
||||
map,
|
||||
switchMap,
|
||||
} from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { getDSORoute } from '../../app-routing-paths';
|
||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||
import { ResearcherProfileDataService } from '../../core/profile/researcher-profile-data.service';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
import {
|
||||
getFirstCompletedRemoteData,
|
||||
getRemoteDataPayload,
|
||||
} from '../../core/shared/operators';
|
||||
import { CorrectionTypeDataService } from '../../core/submission/correctiontype-data.service';
|
||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||
import {
|
||||
hasNoValue,
|
||||
hasValue,
|
||||
isNotEmpty,
|
||||
} from '../empty.util';
|
||||
import { MenuService } from '../menu/menu.service';
|
||||
import { MenuID } from '../menu/menu-id.model';
|
||||
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
|
||||
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
|
||||
import { MenuItemType } from '../menu/menu-item-type.model';
|
||||
import { MenuSection } from '../menu/menu-section.model';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { SubscriptionModalComponent } from '../subscriptions/subscription-modal/subscription-modal.component';
|
||||
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||
import {
|
||||
DsoWithdrawnReinstateModalService,
|
||||
REQUEST_REINSTATE,
|
||||
REQUEST_WITHDRAWN,
|
||||
} from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||
import { DSOEditMenuResolverService } from './dso-edit-menu-resolver.service';
|
||||
|
||||
/**
|
||||
* Creates the menus for the dspace object pages
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DSOEditMenuResolver {
|
||||
|
||||
constructor(
|
||||
protected dSpaceObjectDataService: DSpaceObjectDataService,
|
||||
protected menuService: MenuService,
|
||||
protected authorizationService: AuthorizationDataService,
|
||||
protected modalService: NgbModal,
|
||||
protected dsoVersioningModalService: DsoVersioningModalService,
|
||||
protected researcherProfileService: ResearcherProfileDataService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService,
|
||||
protected dsoWithdrawnReinstateModalService: DsoWithdrawnReinstateModalService,
|
||||
private correctionTypeDataService: CorrectionTypeDataService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise all dspace object related menus
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{ [key: string]: MenuSection[] }> {
|
||||
let id = route.params.id;
|
||||
if (hasNoValue(id) && hasValue(route.queryParams.scope)) {
|
||||
id = route.queryParams.scope;
|
||||
}
|
||||
if (hasNoValue(id)) {
|
||||
// If there's no ID, we're not on a DSO homepage, so pass on any pre-existing menu route data
|
||||
return observableOf({ ...route.data?.menu });
|
||||
} else {
|
||||
return this.dSpaceObjectDataService.findById(id, true, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
switchMap((dsoRD) => {
|
||||
if (dsoRD.hasSucceeded) {
|
||||
const dso = dsoRD.payload;
|
||||
return combineLatest(this.getDsoMenus(dso, route, state)).pipe(
|
||||
// Menu sections are retrieved as an array of arrays and flattened into a single array
|
||||
map((combinedMenus) => [].concat.apply([], combinedMenus)),
|
||||
map((menus) => this.addDsoUuidToMenuIDs(menus, dso)),
|
||||
map((menus) => {
|
||||
return {
|
||||
...route.data?.menu,
|
||||
[MenuID.DSO_EDIT]: menus,
|
||||
};
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf({ ...route.data?.menu });
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the menus for a dso based on the route and state
|
||||
*/
|
||||
getDsoMenus(dso, route, state): Observable<MenuSection[]>[] {
|
||||
return [
|
||||
this.getItemMenu(dso),
|
||||
this.getComColMenu(dso),
|
||||
this.getCommonMenu(dso, state),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the common menus between all dspace objects
|
||||
*/
|
||||
protected getCommonMenu(dso, state): Observable<MenuSection[]> {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
|
||||
]).pipe(
|
||||
map(([canEditItem]) => {
|
||||
return [
|
||||
{
|
||||
id: 'edit-dso',
|
||||
active: false,
|
||||
visible: canEditItem,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: this.getDsoType(dso) + '.page.edit',
|
||||
link: new URLCombiner(getDSORoute(dso), 'edit', 'metadata').toString(),
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'pencil-alt',
|
||||
index: 2,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item specific menus
|
||||
*/
|
||||
protected getItemMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Item) {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
|
||||
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
|
||||
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create'),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSynchronizeWithORCID, dso.self),
|
||||
this.authorizationService.isAuthorized(FeatureID.CanClaimItem, dso.self),
|
||||
this.correctionTypeDataService.findByItem(dso.uuid, false).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
getRemoteDataPayload()),
|
||||
]).pipe(
|
||||
map(([canCreateVersion, disableVersioning, versionTooltip, canSynchronizeWithOrcid, canClaimItem, correction]) => {
|
||||
const isPerson = this.getDsoType(dso) === 'person';
|
||||
return [
|
||||
{
|
||||
id: 'orcid-dso',
|
||||
active: false,
|
||||
visible: isPerson && canSynchronizeWithOrcid,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
text: 'item.page.orcid.tooltip',
|
||||
link: new URLCombiner(getDSORoute(dso), 'orcid').toString(),
|
||||
} as LinkMenuItemModel,
|
||||
icon: 'orcid fab fa-lg',
|
||||
index: 0,
|
||||
},
|
||||
{
|
||||
id: 'version-dso',
|
||||
active: false,
|
||||
visible: canCreateVersion,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: versionTooltip,
|
||||
disabled: disableVersioning,
|
||||
function: () => {
|
||||
this.dsoVersioningModalService.openCreateVersionModal(dso);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'code-branch',
|
||||
index: 1,
|
||||
},
|
||||
{
|
||||
id: 'claim-dso',
|
||||
active: false,
|
||||
visible: isPerson && canClaimItem,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'item.page.claim.button',
|
||||
function: () => {
|
||||
this.claimResearcher(dso);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'hand-paper',
|
||||
index: 3,
|
||||
},
|
||||
{
|
||||
id: 'withdrawn-item',
|
||||
active: false,
|
||||
visible: dso.isArchived && correction?.page.some((c) => c.topic === REQUEST_WITHDRAWN),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.withdrawn',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-withdrawn', dso.isArchived);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye-slash',
|
||||
index: 4,
|
||||
},
|
||||
{
|
||||
id: 'reinstate-item',
|
||||
active: false,
|
||||
visible: dso.isWithdrawn && correction?.page.some((c) => c.topic === REQUEST_REINSTATE),
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text:'item.page.reinstate',
|
||||
function: () => {
|
||||
this.dsoWithdrawnReinstateModalService.openCreateWithdrawnReinstateModal(dso, 'request-reinstate', dso.isArchived);
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'eye',
|
||||
index: 5,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Community/Collection-specific menus
|
||||
*/
|
||||
protected getComColMenu(dso): Observable<MenuSection[]> {
|
||||
if (dso instanceof Community || dso instanceof Collection) {
|
||||
return combineLatest([
|
||||
this.authorizationService.isAuthorized(FeatureID.CanSubscribe, dso.self),
|
||||
]).pipe(
|
||||
map(([canSubscribe]) => {
|
||||
return [
|
||||
{
|
||||
id: 'subscribe',
|
||||
active: false,
|
||||
visible: canSubscribe,
|
||||
model: {
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'subscriptions.tooltip',
|
||||
function: () => {
|
||||
const modalRef = this.modalService.open(SubscriptionModalComponent);
|
||||
modalRef.componentInstance.dso = dso;
|
||||
},
|
||||
} as OnClickMenuItemModel,
|
||||
icon: 'bell',
|
||||
index: 4,
|
||||
},
|
||||
];
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
return observableOf([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Claim a researcher by creating a profile
|
||||
* Shows notifications and/or hides the menu section on success/error
|
||||
*/
|
||||
protected claimResearcher(dso) {
|
||||
this.researcherProfileService.createFromExternalSourceAndReturnRelatedItemId(dso.self)
|
||||
.subscribe((id: string) => {
|
||||
if (isNotEmpty(id)) {
|
||||
this.notificationsService.success(this.translate.get('researcherprofile.success.claim.title'),
|
||||
this.translate.get('researcherprofile.success.claim.body'));
|
||||
this.authorizationService.invalidateAuthorizationsRequestCache();
|
||||
this.menuService.hideMenuSection(MenuID.DSO_EDIT, 'claim-dso-' + dso.uuid);
|
||||
} else {
|
||||
this.notificationsService.error(
|
||||
this.translate.get('researcherprofile.error.claim.title'),
|
||||
this.translate.get('researcherprofile.error.claim.body'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the dso or entity type for an object to be used in generic messages
|
||||
*/
|
||||
protected getDsoType(dso) {
|
||||
const renderType = dso.getRenderTypes()[0];
|
||||
if (typeof renderType === 'string' || renderType instanceof String) {
|
||||
return renderType.toLowerCase();
|
||||
} else {
|
||||
return dso.type.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the dso uuid to all provided menu ids and parent ids
|
||||
*/
|
||||
protected addDsoUuidToMenuIDs(menus, dso) {
|
||||
return menus.map((menu) => {
|
||||
Object.assign(menu, {
|
||||
id: menu.id + '-' + dso.uuid,
|
||||
});
|
||||
if (hasValue(menu.parentID)) {
|
||||
Object.assign(menu, {
|
||||
parentID: menu.parentID + '-' + dso.uuid,
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
export const DSOEditMenuResolver: ResolveFn<{ [key: string]: MenuSection[] }> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
menuResolverService: DSOEditMenuResolverService = inject(DSOEditMenuResolverService),
|
||||
): Observable<{ [key: string]: MenuSection[] }> => {
|
||||
return menuResolverService.resolve(route, state);
|
||||
};
|
||||
|
@@ -53,6 +53,7 @@ import {
|
||||
DynamicFormValidationService,
|
||||
DynamicTemplateDirective,
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormControlMapFn } from '@ng-dynamic-forms/core/lib/service/dynamic-form-component.service';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
TranslateModule,
|
||||
@@ -74,7 +75,6 @@ import {
|
||||
import {
|
||||
APP_CONFIG,
|
||||
AppConfig,
|
||||
DynamicFormControlFn,
|
||||
} from '../../../../../config/app-config.interface';
|
||||
import { AppState } from '../../../../app.reducer';
|
||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||
@@ -214,7 +214,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
||||
public formBuilderService: FormBuilderService,
|
||||
private submissionService: SubmissionService,
|
||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||
@Inject(DYNAMIC_FORM_CONTROL_MAP_FN) protected dynamicFormControlFn: DynamicFormControlFn,
|
||||
@Inject(DYNAMIC_FORM_CONTROL_MAP_FN) protected dynamicFormControlFn: DynamicFormControlMapFn,
|
||||
) {
|
||||
super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService);
|
||||
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
|
||||
|
@@ -1,21 +1,18 @@
|
||||
import {
|
||||
Inject,
|
||||
Injectable,
|
||||
inject,
|
||||
InjectionToken,
|
||||
Injector,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
|
||||
import {
|
||||
APP_DATA_SERVICES_MAP,
|
||||
LazyDataServicesMap,
|
||||
} from '../../../../config/app-config.interface';
|
||||
import { LazyDataServicesMap } from '../../../../config/app-config.interface';
|
||||
import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { lazyService } from '../../../core/lazy-service';
|
||||
@@ -25,34 +22,31 @@ import { ResourceType } from '../../../core/shared/resource-type';
|
||||
import { isEmpty } from '../../empty.util';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ResourcePolicyTargetResolver {
|
||||
|
||||
constructor(
|
||||
private parentInjector: Injector,
|
||||
private router: Router,
|
||||
@Inject(APP_DATA_SERVICES_MAP) private dataServiceMap: InjectionToken<LazyDataServicesMap>) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param dataServiceMap
|
||||
* @param parentInjector
|
||||
* @param router
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<DSpaceObject>> {
|
||||
export const ResourcePolicyTargetResolver: ResolveFn<RemoteData<DSpaceObject>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
dataServiceMap: InjectionToken<LazyDataServicesMap> = inject(InjectionToken<LazyDataServicesMap>),
|
||||
parentInjector: Injector = inject(Injector),
|
||||
router: Router = inject(Router),
|
||||
): Observable<RemoteData<DSpaceObject>> => {
|
||||
const targetType = route.queryParamMap.get('targetType');
|
||||
const policyTargetId = route.queryParamMap.get('policyTargetId');
|
||||
|
||||
if (isEmpty(targetType) || isEmpty(policyTargetId)) {
|
||||
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||
router.navigateByUrl('/404', { skipLocationChange: true });
|
||||
}
|
||||
|
||||
const resourceType: ResourceType = new ResourceType(targetType);
|
||||
const lazyProvider$: Observable<IdentifiableDataService<DSpaceObject>> = lazyService(this.dataServiceMap[resourceType.value], this.parentInjector);
|
||||
const lazyProvider$: Observable<IdentifiableDataService<DSpaceObject>> = lazyService(dataServiceMap[resourceType.value], parentInjector);
|
||||
|
||||
return lazyProvider$.pipe(
|
||||
switchMap((dataService: IdentifiableDataService<DSpaceObject>) => {
|
||||
@@ -60,5 +54,4 @@ export class ResourcePolicyTargetResolver {
|
||||
}),
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
Router,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
@@ -14,30 +15,27 @@ import { isEmpty } from '../../empty.util';
|
||||
import { followLink } from '../../utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ResourcePolicyResolver {
|
||||
|
||||
constructor(private resourcePolicyService: ResourcePolicyDataService, private router: Router) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving an item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {Router} router
|
||||
* @param {ResourcePolicyDataService} resourcePolicyService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<ResourcePolicy>> {
|
||||
export const ResourcePolicyResolver: ResolveFn<RemoteData<ResourcePolicy>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
router: Router = inject(Router),
|
||||
resourcePolicyService: ResourcePolicyDataService = inject(ResourcePolicyDataService),
|
||||
): Observable<RemoteData<ResourcePolicy>> => {
|
||||
const policyId = route.queryParamMap.get('policyId');
|
||||
|
||||
if (isEmpty(policyId)) {
|
||||
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
||||
router.navigateByUrl('/404', { skipLocationChange: true });
|
||||
}
|
||||
|
||||
return this.resourcePolicyService.findById(policyId, true, false, followLink('eperson'), followLink('group')).pipe(
|
||||
return resourcePolicyService.findById(policyId, true, false, followLink('eperson'), followLink('group')).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -12,23 +13,19 @@ import { SuggestionTargetDataService } from '../core/notifications/target/sugges
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SuggestionsPageResolver {
|
||||
constructor(private suggestionsDataService: SuggestionTargetDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a suggestion target based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {SuggestionTargetDataService} suggestionsDataService
|
||||
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<SuggestionTarget>> {
|
||||
return this.suggestionsDataService.getTargetById(route.params.targetId).pipe(
|
||||
export const SuggestionsPageResolver: ResolveFn<RemoteData<SuggestionTarget>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
suggestionsDataService: SuggestionTargetDataService = inject(SuggestionTargetDataService),
|
||||
): Observable<RemoteData<SuggestionTarget>> => {
|
||||
return suggestionsDataService.getTargetById(route.params.targetId).pipe(
|
||||
find((RD) => hasValue(RD.hasFailed) || RD.hasSucceeded),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@ import { ItemFromWorkflowResolver } from './item-from-workflow.resolver';
|
||||
|
||||
describe('ItemFromWorkflowResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: ItemFromWorkflowResolver;
|
||||
let resolver: any;
|
||||
let wfiService: WorkflowItemDataService;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
const itemUuid = '8888-8888-8888-8888';
|
||||
@@ -20,11 +20,11 @@ describe('ItemFromWorkflowResolver', () => {
|
||||
wfiService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
||||
} as any;
|
||||
resolver = new ItemFromWorkflowResolver(wfiService, null);
|
||||
resolver = ItemFromWorkflowResolver;
|
||||
});
|
||||
|
||||
it('should resolve a an item from from the workflow item with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver({ params: { id: uuid } } as any, undefined, wfiService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,20 +1,21 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
||||
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemFromWorkflowResolver extends SubmissionObjectResolver<Item> {
|
||||
constructor(
|
||||
private workflowItemService: WorkflowItemDataService,
|
||||
protected store: Store<any>,
|
||||
) {
|
||||
super(workflowItemService, store);
|
||||
}
|
||||
export const ItemFromWorkflowResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
workflowItemService: WorkflowItemDataService = inject(WorkflowItemDataService),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return SubmissionObjectResolver(route, state, workflowItemService);
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -6,7 +6,7 @@ import { WorkflowItemPageResolver } from './workflow-item-page.resolver';
|
||||
|
||||
describe('WorkflowItemPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: WorkflowItemPageResolver;
|
||||
let resolver: any;
|
||||
let wfiService: WorkflowItemDataService;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
@@ -14,11 +14,11 @@ describe('WorkflowItemPageResolver', () => {
|
||||
wfiService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||
} as any;
|
||||
resolver = new WorkflowItemPageResolver(wfiService);
|
||||
resolver = WorkflowItemPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a workflow item with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver({ params: { id: uuid } } as any, undefined, wfiService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
@@ -11,28 +12,17 @@ import { WorkflowItem } from '../core/submission/models/workflowitem.model';
|
||||
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific workflow item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class WorkflowItemPageResolver {
|
||||
constructor(private workflowItemService: WorkflowItemDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a workflow item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found workflow item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
|
||||
return this.workflowItemService.findById(route.params.id,
|
||||
export const WorkflowItemPageResolver: ResolveFn<RemoteData<WorkflowItem>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
workflowItemService: WorkflowItemDataService = inject(WorkflowItemDataService),
|
||||
): Observable<RemoteData<WorkflowItem>> => {
|
||||
return workflowItemService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@ import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
|
||||
|
||||
describe('ItemFromWorkspaceResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: ItemFromWorkspaceResolver;
|
||||
let resolver: any;
|
||||
let wfiService: WorkspaceitemDataService;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
const itemUuid = '8888-8888-8888-8888';
|
||||
@@ -20,11 +20,11 @@ describe('ItemFromWorkspaceResolver', () => {
|
||||
wfiService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
||||
} as any;
|
||||
resolver = new ItemFromWorkspaceResolver(wfiService, null);
|
||||
resolver = ItemFromWorkspaceResolver;
|
||||
});
|
||||
|
||||
it('should resolve a an item from from the workflow item with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver({ params: { id: uuid } } as any, undefined, wfiService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,20 +1,23 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific item before the route is activated
|
||||
* This method represents a resolver that requests a specific item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ItemFromWorkspaceResolver extends SubmissionObjectResolver<Item> {
|
||||
constructor(
|
||||
private workspaceItemService: WorkspaceitemDataService,
|
||||
protected store: Store<any>,
|
||||
) {
|
||||
super(workspaceItemService, store);
|
||||
}
|
||||
|
||||
}
|
||||
export const ItemFromWorkspaceResolver: ResolveFn<RemoteData<Item>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
workspaceItemService: WorkspaceitemDataService = inject(WorkspaceitemDataService),
|
||||
): Observable<RemoteData<Item>> => {
|
||||
return SubmissionObjectResolver(route, state, workspaceItemService);
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@ import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
|
||||
|
||||
describe('WorkflowItemPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: WorkspaceItemPageResolver;
|
||||
let resolver: any;
|
||||
let wsiService: WorkspaceitemDataService;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
@@ -14,11 +14,11 @@ describe('WorkflowItemPageResolver', () => {
|
||||
wsiService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||
} as any;
|
||||
resolver = new WorkspaceItemPageResolver(wsiService);
|
||||
resolver = WorkspaceItemPageResolver;
|
||||
});
|
||||
|
||||
it('should resolve a workspace item with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver({ params: { id: uuid } } as any, undefined, wsiService)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -1,38 +1,35 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject } from '@angular/core';
|
||||
import {
|
||||
ActivatedRouteSnapshot,
|
||||
ResolveFn,
|
||||
RouterStateSnapshot,
|
||||
} from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
|
||||
import { WorkspaceItem } from '../core/submission/models/workspaceitem.model';
|
||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific workflow item before the route is activated
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class WorkspaceItemPageResolver {
|
||||
constructor(private workspaceItemService: WorkspaceitemDataService) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for resolving a workflow item based on the parameters in the current route
|
||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||
* @param {WorkspaceitemDataService} workspaceItemService
|
||||
* @returns Observable<<RemoteData<Item>> Emits the found workflow item based on the parameters in the current route,
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
|
||||
return this.workspaceItemService.findById(route.params.id,
|
||||
export const WorkspaceItemPageResolver: ResolveFn<RemoteData<WorkspaceItem>> = (
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
workspaceItemService: WorkspaceitemDataService = inject(WorkspaceitemDataService),
|
||||
): Observable<RemoteData<WorkspaceItem>> => {
|
||||
return workspaceItemService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('item'),
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -3,7 +3,6 @@ import {
|
||||
makeStateKey,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
import { DynamicFormControl } from '@ng-dynamic-forms/core/lib/component/dynamic-form-control-interface';
|
||||
|
||||
import { AdminNotifyMetricsRow } from '../app/admin/admin-notify-dashboard/admin-notify-metrics/admin-notify-metrics.model';
|
||||
import { HALDataService } from '../app/core/data/base/hal-data-service.interface';
|
||||
@@ -78,11 +77,8 @@ export interface LazyDataServicesMap {
|
||||
[type: string]: () => Promise<Type<HALDataService<any>>>
|
||||
}
|
||||
|
||||
export type DynamicFormControlFn = (model: string) => Type<DynamicFormControl>;
|
||||
export const APP_DATA_SERVICES_MAP: InjectionToken<LazyDataServicesMap> = new InjectionToken<LazyDataServicesMap>('APP_DATA_SERVICES_MAP');
|
||||
|
||||
export const APP_DYNAMIC_FORM_CONTROL_FN: InjectionToken<DynamicFormControlFn> = new InjectionToken<DynamicFormControlFn>('APP_DYNAMIC_FORM_CONTROL_FN');
|
||||
|
||||
export {
|
||||
APP_CONFIG,
|
||||
APP_CONFIG_STATE,
|
||||
|
Reference in New Issue
Block a user