mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +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 {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -11,24 +12,20 @@ import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
|||||||
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific bitstreamFormat before the route is activated
|
* Method for resolving an bitstreamFormat based on the parameters in the current route
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const BitstreamFormatsResolver: ResolveFn<RemoteData<BitstreamFormat>> = (
|
||||||
export class BitstreamFormatsResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
bitstreamFormatDataService: BitstreamFormatDataService = inject(BitstreamFormatDataService),
|
||||||
|
): Observable<RemoteData<BitstreamFormat>> => {
|
||||||
/**
|
return bitstreamFormatDataService.findById(route.params.id)
|
||||||
* Method for resolving an bitstreamFormat based on the parameters in the current route
|
.pipe(
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
getFirstCompletedRemoteData(),
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
);
|
||||||
* @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)
|
|
||||||
.pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
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
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const BitstreamPageResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||||
export class BitstreamPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(private bitstreamService: BitstreamDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
bitstreamService: BitstreamDataService = inject(BitstreamDataService),
|
||||||
|
): Observable<RemoteData<Bitstream>> => {
|
||||||
/**
|
return bitstreamService.findById(route.params.id, true, false, ...BITSTREAM_PAGE_LINKS_TO_FOLLOW)
|
||||||
* Method for resolving a bitstream based on the parameters in the current route
|
.pipe(
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
getFirstCompletedRemoteData(),
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
);
|
||||||
* @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)
|
|
||||||
.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';
|
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||||
|
|
||||||
describe(`LegacyBitstreamUrlResolver`, () => {
|
describe(`LegacyBitstreamUrlResolver`, () => {
|
||||||
let resolver: LegacyBitstreamUrlResolver;
|
let resolver: any;
|
||||||
let bitstreamDataService: BitstreamDataService;
|
let bitstreamDataService: BitstreamDataService;
|
||||||
let testScheduler;
|
let testScheduler;
|
||||||
let remoteDataMocks;
|
let remoteDataMocks;
|
||||||
@@ -33,7 +33,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
|||||||
bitstreamDataService = {
|
bitstreamDataService = {
|
||||||
findByItemHandle: () => undefined,
|
findByItemHandle: () => undefined,
|
||||||
} as any;
|
} as any;
|
||||||
resolver = new LegacyBitstreamUrlResolver(bitstreamDataService);
|
resolver = LegacyBitstreamUrlResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`resolve`, () => {
|
describe(`resolve`, () => {
|
||||||
@@ -51,7 +51,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
|||||||
});
|
});
|
||||||
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
|
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
|
||||||
testScheduler.run(() => {
|
testScheduler.run(() => {
|
||||||
resolver.resolve(route, state);
|
resolver(route, state, bitstreamDataService);
|
||||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||||
`${route.params.prefix}/${route.params.suffix}`,
|
`${route.params.prefix}/${route.params.suffix}`,
|
||||||
route.params.sequence_id,
|
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`, () => {
|
it(`should call findByItemHandle with the handle and filename from the route, and the sequence ID from the queryParams`, () => {
|
||||||
testScheduler.run(() => {
|
testScheduler.run(() => {
|
||||||
resolver.resolve(route, state);
|
resolver(route, state, bitstreamDataService);
|
||||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||||
`${route.params.prefix}/${route.params.suffix}`,
|
`${route.params.prefix}/${route.params.suffix}`,
|
||||||
route.queryParams.sequenceId,
|
route.queryParams.sequenceId,
|
||||||
@@ -100,7 +100,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
|||||||
});
|
});
|
||||||
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
|
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
|
||||||
testScheduler.run(() => {
|
testScheduler.run(() => {
|
||||||
resolver.resolve(route, state);
|
resolver(route, state, bitstreamDataService);
|
||||||
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
|
||||||
`${route.params.prefix}/${route.params.suffix}`,
|
`${route.params.prefix}/${route.params.suffix}`,
|
||||||
undefined,
|
undefined,
|
||||||
@@ -123,7 +123,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
|||||||
c: remoteDataMocks.Error,
|
c: remoteDataMocks.Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
expectObservable(resolver.resolve(route, state)).toBe(expected, values);
|
expectObservable(resolver(route, state, bitstreamDataService)).toBe(expected, values);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it(`...succeeded`, () => {
|
it(`...succeeded`, () => {
|
||||||
@@ -138,7 +138,7 @@ describe(`LegacyBitstreamUrlResolver`, () => {
|
|||||||
c: remoteDataMocks.Success,
|
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 {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -12,41 +13,34 @@ import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
|||||||
import { hasNoValue } from '../shared/empty.util';
|
import { hasNoValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class resolves a bitstream based on the DSpace 6 XMLUI or JSPUI bitstream download URLs
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const LegacyBitstreamUrlResolver: ResolveFn<RemoteData<Bitstream>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class LegacyBitstreamUrlResolver {
|
bitstreamDataService: BitstreamDataService = inject(BitstreamDataService),
|
||||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
): Observable<RemoteData<Bitstream>> => {
|
||||||
|
const prefix = route.params.prefix;
|
||||||
|
const suffix = route.params.suffix;
|
||||||
|
const filename = route.params.filename;
|
||||||
|
|
||||||
|
let sequenceId = route.params.sequence_id;
|
||||||
|
if (hasNoValue(sequenceId)) {
|
||||||
|
sequenceId = route.queryParams.sequenceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return bitstreamDataService.findByItemHandle(
|
||||||
* Resolve a bitstream based on the handle of the item, and the sequence id or the filename of the
|
`${prefix}/${suffix}`,
|
||||||
* bitstream
|
sequenceId,
|
||||||
*
|
filename,
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
).pipe(
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
getFirstCompletedRemoteData(),
|
||||||
* @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>> {
|
|
||||||
const prefix = route.params.prefix;
|
|
||||||
const suffix = route.params.suffix;
|
|
||||||
const filename = route.params.filename;
|
|
||||||
|
|
||||||
let sequenceId = route.params.sequence_id;
|
|
||||||
if (hasNoValue(sequenceId)) {
|
|
||||||
sequenceId = route.queryParams.sequenceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.bitstreamDataService.findByItemHandle(
|
|
||||||
`${prefix}/${suffix}`,
|
|
||||||
sequenceId,
|
|
||||||
filename,
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -19,30 +20,28 @@ import {
|
|||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject on a browse by page
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const BrowseByDSOBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community | Collection>> = (
|
||||||
export class BrowseByDSOBreadcrumbResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: DSpaceObjectDataService) {
|
state: RouterStateSnapshot,
|
||||||
|
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||||
|
dataService: DSpaceObjectDataService = inject(DSpaceObjectDataService),
|
||||||
|
): Observable<BreadcrumbConfig<Community | Collection>> => {
|
||||||
|
const uuid = route.queryParams.scope;
|
||||||
|
if (hasValue(uuid)) {
|
||||||
|
return dataService.findById(uuid).pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((object: Community | Collection) => {
|
||||||
|
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
/**
|
};
|
||||||
* Method for resolving a breadcrumb config object
|
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
|
||||||
* @returns BreadcrumbConfig object
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Community | Collection>> {
|
|
||||||
const uuid = route.queryParams.scope;
|
|
||||||
if (hasValue(uuid)) {
|
|
||||||
return this.dataService.findById(uuid).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((object: Community | Collection) => {
|
|
||||||
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,32 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from '../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
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
|
* Method for resolving a browse-by i18n breadcrumb configuration object
|
||||||
* It adds the metadata field of the current browse-by page
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns BreadcrumbConfig object for a browse-by page
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const BrowseByI18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||||
export class BrowseByI18nBreadcrumbResolver extends I18nBreadcrumbResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(protected breadcrumbService: I18nBreadcrumbsService) {
|
state: RouterStateSnapshot,
|
||||||
super(breadcrumbService);
|
): BreadcrumbConfig<string> => {
|
||||||
}
|
const extendedBreadcrumbKey = route.data.breadcrumbKey + '.' + route.params.id;
|
||||||
|
route.data = Object.assign({}, route.data, { breadcrumbKey: extendedBreadcrumbKey });
|
||||||
/**
|
return I18nBreadcrumbResolver(route, state) as BreadcrumbConfig<string>;
|
||||||
* 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> {
|
|
||||||
const extendedBreadcrumbKey = route.data.breadcrumbKey + '.' + route.params.id;
|
|
||||||
route.data = Object.assign({}, route.data, { breadcrumbKey: extendedBreadcrumbKey });
|
|
||||||
return super.resolve(route, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Observable } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
@@ -5,7 +6,7 @@ import { CollectionPageResolver } from './collection-page.resolver';
|
|||||||
|
|
||||||
describe('CollectionPageResolver', () => {
|
describe('CollectionPageResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: CollectionPageResolver;
|
let resolver: any;
|
||||||
let collectionService: any;
|
let collectionService: any;
|
||||||
let store: any;
|
let store: any;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
@@ -17,12 +18,11 @@ describe('CollectionPageResolver', () => {
|
|||||||
store = jasmine.createSpyObj('store', {
|
store = jasmine.createSpyObj('store', {
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
});
|
});
|
||||||
resolver = new CollectionPageResolver(collectionService, store);
|
resolver = CollectionPageResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve a collection with the correct id', (done) => {
|
it('should resolve a collection 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, collectionService, store) as Observable<any>).pipe(first())
|
||||||
.pipe(first())
|
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
expect(resolved.payload.id).toEqual(uuid);
|
expect(resolved.payload.id).toEqual(uuid);
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||||
@@ -28,37 +30,32 @@ export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific collection before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const CollectionPageResolver: ResolveFn<RemoteData<Collection>> = (
|
||||||
export class CollectionPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
private collectionService: CollectionDataService,
|
collectionService: CollectionDataService = inject(CollectionDataService),
|
||||||
private store: Store<any>,
|
store: Store<AppState> = inject(Store<AppState>),
|
||||||
) {
|
): Observable<RemoteData<Collection>> => {
|
||||||
}
|
const collectionRD$ = collectionService.findById(
|
||||||
|
route.params.id,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
...COLLECTION_PAGE_LINKS_TO_FOLLOW,
|
||||||
|
).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
||||||
* Method for resolving a collection based on the parameters in the current route
|
store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
});
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
|
||||||
* @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(
|
|
||||||
route.params.id,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
...COLLECTION_PAGE_LINKS_TO_FOLLOW,
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
|
|
||||||
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
return collectionRD$;
|
||||||
this.store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
};
|
||||||
});
|
|
||||||
|
|
||||||
return collectionRD$;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,27 +1,24 @@
|
|||||||
|
import { Observable } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
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 { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { ItemTemplatePageResolver } from './item-template-page.resolver';
|
import { ItemTemplatePageResolver } from './item-template-page.resolver';
|
||||||
|
|
||||||
describe('ItemTemplatePageResolver', () => {
|
describe('ItemTemplatePageResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: ItemTemplatePageResolver;
|
let resolver: any;
|
||||||
let itemTemplateService: any;
|
let itemTemplateService: any;
|
||||||
let dsoNameService: DSONameServiceMock;
|
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
itemTemplateService = {
|
itemTemplateService = {
|
||||||
findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
findByCollectionID: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||||
};
|
};
|
||||||
dsoNameService = new DSONameServiceMock();
|
resolver = ItemTemplatePageResolver;
|
||||||
resolver = new ItemTemplatePageResolver(dsoNameService as DSONameService, itemTemplateService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve an item template with the correct id', (done) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,38 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
|
||||||
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
/**
|
export const ItemTemplatePageResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
* This class represents a resolver that requests a specific collection's item template before the route is activated
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
itemTemplateService: ItemTemplateDataService = inject(ItemTemplateDataService),
|
||||||
export class ItemTemplatePageResolver {
|
): Observable<RemoteData<Item>> => {
|
||||||
constructor(
|
return itemTemplateService.findByCollectionID(route.params.id, true, false, followLink('templateItemOf')).pipe(
|
||||||
public dsoNameService: DSONameService,
|
getFirstCompletedRemoteData(),
|
||||||
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(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { Observable } from 'rxjs';
|
||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
@@ -5,7 +6,7 @@ import { CommunityPageResolver } from './community-page.resolver';
|
|||||||
|
|
||||||
describe('CommunityPageResolver', () => {
|
describe('CommunityPageResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: CommunityPageResolver;
|
let resolver: any;
|
||||||
let communityService: any;
|
let communityService: any;
|
||||||
let store: any;
|
let store: any;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
@@ -17,11 +18,11 @@ describe('CommunityPageResolver', () => {
|
|||||||
store = jasmine.createSpyObj('store', {
|
store = jasmine.createSpyObj('store', {
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
});
|
});
|
||||||
resolver = new CommunityPageResolver(communityService, store);
|
resolver = CommunityPageResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve a community with the correct id', (done) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||||
@@ -28,37 +30,32 @@ export const COMMUNITY_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Community>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific community before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const CommunityPageResolver: ResolveFn<RemoteData<Community>> = (
|
||||||
export class CommunityPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
private communityService: CommunityDataService,
|
communityService: CommunityDataService = inject(CommunityDataService),
|
||||||
private store: Store<any>,
|
store: Store<AppState> = inject(Store<AppState>),
|
||||||
) {
|
): Observable<RemoteData<Community>> => {
|
||||||
}
|
const communityRD$ = communityService.findById(
|
||||||
|
route.params.id,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
...COMMUNITY_PAGE_LINKS_TO_FOLLOW,
|
||||||
|
).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
||||||
* Method for resolving a community based on the parameters in the current route
|
store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
});
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
|
||||||
* @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(
|
|
||||||
route.params.id,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
...COMMUNITY_PAGE_LINKS_TO_FOLLOW,
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
|
|
||||||
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
return communityRD$;
|
||||||
this.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 { 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 { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { BitstreamDataService } from '../data/bitstream-data.service';
|
import { BitstreamDataService } from '../data/bitstream-data.service';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { BitstreamBreadcrumbsService } from './bitstream-breadcrumbs.service';
|
import { BitstreamBreadcrumbsService } from './bitstream-breadcrumbs.service';
|
||||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
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({
|
export const BitstreamBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Bitstream>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class BitstreamBreadcrumbResolver extends DSOBreadcrumbResolver<Bitstream> {
|
breadcrumbService: BitstreamBreadcrumbsService = inject(BitstreamBreadcrumbsService),
|
||||||
constructor(
|
dataService: BitstreamDataService = inject(BitstreamDataService),
|
||||||
protected breadcrumbService: BitstreamBreadcrumbsService, protected dataService: BitstreamDataService) {
|
): Observable<BreadcrumbConfig<Bitstream>> => {
|
||||||
super(breadcrumbService, dataService);
|
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 { COLLECTION_PAGE_LINKS_TO_FOLLOW } from '../../collection-page/collection-page.resolver';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { CollectionDataService } from '../data/collection-data.service';
|
import { CollectionDataService } from '../data/collection-data.service';
|
||||||
import { Collection } from '../shared/collection.model';
|
import { Collection } from '../shared/collection.model';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
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({
|
export const CollectionBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Collection>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collection> {
|
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CollectionDataService) {
|
dataService: CollectionDataService = inject(CollectionDataService),
|
||||||
super(breadcrumbService, dataService);
|
): 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 { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { CommunityDataService } from '../data/community-data.service';
|
import { CommunityDataService } from '../data/community-data.service';
|
||||||
import { Community } from '../shared/community.model';
|
import { Community } from '../shared/community.model';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
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({
|
export const CommunityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community> {
|
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: CommunityDataService) {
|
dataService: CommunityDataService = inject(CommunityDataService),
|
||||||
super(breadcrumbService, dataService);
|
): Observable<BreadcrumbConfig<Community>> => {
|
||||||
}
|
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||||
|
return DSOBreadcrumbResolver(
|
||||||
/**
|
route,
|
||||||
* Method that returns the follow links to already resolve
|
state,
|
||||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
breadcrumbService,
|
||||||
* Requesting them as embeds will limit the number of requests
|
dataService,
|
||||||
*/
|
...linksToFollow,
|
||||||
get followLinks(): FollowLinkConfig<Community>[] {
|
) as Observable<BreadcrumbConfig<Community>>;
|
||||||
return COMMUNITY_PAGE_LINKS_TO_FOLLOW;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -3,11 +3,10 @@ import { getTestScheduler } from 'jasmine-marbles';
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { Collection } from '../shared/collection.model';
|
import { Collection } from '../shared/collection.model';
|
||||||
import { CollectionBreadcrumbResolver } from './collection-breadcrumb.resolver';
|
import { CollectionBreadcrumbResolver } from './collection-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
|
||||||
|
|
||||||
describe('DSOBreadcrumbResolver', () => {
|
describe('DSOBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: DSOBreadcrumbResolver<Collection>;
|
let resolver: any;
|
||||||
let collectionService: any;
|
let collectionService: any;
|
||||||
let dsoBreadcrumbService: any;
|
let dsoBreadcrumbService: any;
|
||||||
let testCollection: Collection;
|
let testCollection: Collection;
|
||||||
@@ -17,18 +16,18 @@ describe('DSOBreadcrumbResolver', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
uuid = '1234-65487-12354-1235';
|
uuid = '1234-65487-12354-1235';
|
||||||
breadcrumbUrl = '/collections/' + uuid;
|
breadcrumbUrl = `/collections/${uuid}`;
|
||||||
currentUrl = breadcrumbUrl + '/edit';
|
currentUrl = `${breadcrumbUrl}/edit`;
|
||||||
testCollection = Object.assign(new Collection(), { uuid });
|
testCollection = Object.assign(new Collection(), { uuid });
|
||||||
dsoBreadcrumbService = {};
|
dsoBreadcrumbService = {};
|
||||||
collectionService = {
|
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', () => {
|
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 };
|
const expectedConfig = { provider: dsoBreadcrumbService, key: testCollection, url: breadcrumbUrl };
|
||||||
getTestScheduler().expectObservable(resolvedConfig).toBe('(a|)', { a: expectedConfig });
|
getTestScheduler().expectObservable(resolvedConfig).toBe('(a|)', { a: expectedConfig });
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
@@ -10,7 +9,6 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
|||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||||
import { ChildHALResource } from '../shared/child-hal-resource.model';
|
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
@@ -19,45 +17,33 @@ import {
|
|||||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceObject> {
|
breadcrumbService: DSOBreadcrumbsService,
|
||||||
protected constructor(
|
dataService: IdentifiableDataService<DSpaceObject>,
|
||||||
protected breadcrumbService: DSOBreadcrumbsService,
|
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
|
||||||
protected dataService: IdentifiableDataService<T>,
|
): Observable<BreadcrumbConfig<DSpaceObject>> => {
|
||||||
) {
|
const uuid = route.params.id;
|
||||||
}
|
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
/**
|
getRemoteDataPayload(),
|
||||||
* Method for resolving a breadcrumb config object
|
map((object: DSpaceObject) => {
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
if (hasValue(object)) {
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
const fullPath = state.url;
|
||||||
* @returns BreadcrumbConfig object
|
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
|
||||||
*/
|
return { provider: breadcrumbService, key: object, url: url };
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
|
} else {
|
||||||
const uuid = route.params.id;
|
return undefined;
|
||||||
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
|
}
|
||||||
getFirstCompletedRemoteData(),
|
}),
|
||||||
getRemoteDataPayload(),
|
);
|
||||||
map((object: T) => {
|
};
|
||||||
if (hasValue(object)) {
|
|
||||||
const fullPath = state.url;
|
|
||||||
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
|
|
||||||
return { provider: this.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('I18nBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: I18nBreadcrumbResolver;
|
let resolver: any;
|
||||||
let i18nBreadcrumbService: any;
|
let i18nBreadcrumbService: any;
|
||||||
let i18nKey: string;
|
let i18nKey: string;
|
||||||
let route: any;
|
let route: any;
|
||||||
@@ -27,18 +27,18 @@ describe('I18nBreadcrumbResolver', () => {
|
|||||||
};
|
};
|
||||||
expectedPath = new URLCombiner(parentSegment, segment).toString();
|
expectedPath = new URLCombiner(parentSegment, segment).toString();
|
||||||
i18nBreadcrumbService = {};
|
i18nBreadcrumbService = {};
|
||||||
resolver = new I18nBreadcrumbResolver(i18nBreadcrumbService);
|
resolver = I18nBreadcrumbResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the breadcrumb config', () => {
|
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 };
|
const expectedConfig = { provider: i18nBreadcrumbService, key: i18nKey, url: expectedPath };
|
||||||
expect(resolvedConfig).toEqual(expectedConfig);
|
expect(resolvedConfig).toEqual(expectedConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
resolver.resolve({ data: {} } as any, undefined);
|
resolver({ data: {} } as any, undefined, i18nBreadcrumbService);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
@@ -10,27 +11,21 @@ import { currentPathFromSnapshot } from '../../shared/utils/route.utils';
|
|||||||
import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from './i18n-breadcrumbs.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const I18nBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class I18nBreadcrumbResolver {
|
breadcrumbService: I18nBreadcrumbsService = inject(I18nBreadcrumbsService),
|
||||||
constructor(protected breadcrumbService: 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: breadcrumbService, key: key, url: fullPath };
|
||||||
* Method for resolving an I18n breadcrumb configuration object
|
};
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
|
||||||
* @returns BreadcrumbConfig object
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 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 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -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 { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page/item.resolver';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { ItemDataService } from '../data/item-data.service';
|
import { ItemDataService } from '../data/item-data.service';
|
||||||
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
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({
|
export const ItemBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Item>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
breadcrumbService: DSOBreadcrumbsService = inject(DSOBreadcrumbsService),
|
||||||
constructor(protected breadcrumbService: DSOBreadcrumbsService, protected dataService: ItemDataService) {
|
dataService: ItemDataService = inject(ItemDataService),
|
||||||
super(breadcrumbService, dataService);
|
): Observable<BreadcrumbConfig<Item>> => {
|
||||||
}
|
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = ITEM_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
|
||||||
|
return DSOBreadcrumbResolver(
|
||||||
/**
|
route,
|
||||||
* Method that returns the follow links to already resolve
|
state,
|
||||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
breadcrumbService,
|
||||||
* Requesting them as embeds will limit the number of requests
|
dataService,
|
||||||
*/
|
...linksToFollow,
|
||||||
get followLinks(): FollowLinkConfig<Item>[] {
|
) as Observable<BreadcrumbConfig<Item>>;
|
||||||
return ITEM_PAGE_LINKS_TO_FOLLOW;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -2,7 +2,7 @@ import { NavigationBreadcrumbResolver } from './navigation-breadcrumb.resolver';
|
|||||||
|
|
||||||
describe('NavigationBreadcrumbResolver', () => {
|
describe('NavigationBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: NavigationBreadcrumbResolver;
|
let resolver: any;
|
||||||
let NavigationBreadcrumbService: any;
|
let NavigationBreadcrumbService: any;
|
||||||
let i18nKey: string;
|
let i18nKey: string;
|
||||||
let relatedI18nKey: string;
|
let relatedI18nKey: string;
|
||||||
@@ -40,11 +40,11 @@ describe('NavigationBreadcrumbResolver', () => {
|
|||||||
};
|
};
|
||||||
expectedPath = '/base/example:/base';
|
expectedPath = '/base/example:/base';
|
||||||
NavigationBreadcrumbService = {};
|
NavigationBreadcrumbService = {};
|
||||||
resolver = new NavigationBreadcrumbResolver(NavigationBreadcrumbService);
|
resolver = NavigationBreadcrumbResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the breadcrumb config', () => {
|
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 };
|
const expectedConfig = { provider: NavigationBreadcrumbService, key: `${i18nKey}:${relatedI18nKey}`, url: expectedPath };
|
||||||
expect(resolvedConfig).toEqual(expectedConfig);
|
expect(resolvedConfig).toEqual(expectedConfig);
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
@@ -8,49 +9,44 @@ import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config
|
|||||||
import { NavigationBreadcrumbsService } from './navigation-breadcrumb.service';
|
import { NavigationBreadcrumbsService } from './navigation-breadcrumb.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The class that resolves a BreadcrumbConfig object with an i18n key string for a route and related parents
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const NavigationBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class NavigationBreadcrumbResolver {
|
breadcrumbService: NavigationBreadcrumbsService = inject(NavigationBreadcrumbsService),
|
||||||
|
): BreadcrumbConfig<string> => {
|
||||||
private parentRoutes: ActivatedRouteSnapshot[] = [];
|
const parentRoutes: ActivatedRouteSnapshot[] = [];
|
||||||
constructor(protected breadcrumbService: NavigationBreadcrumbsService) {
|
getParentRoutes(route, parentRoutes);
|
||||||
}
|
const relatedRoutes = route.data.relatedRoutes;
|
||||||
|
const parentPaths = parentRoutes.map(parent => parent.routeConfig?.path);
|
||||||
/**
|
const relatedParentRoutes = relatedRoutes.filter(relatedRoute => parentPaths.includes(relatedRoute.path));
|
||||||
* Method to collect all parent routes snapshot from current route snapshot
|
const baseUrlSegmentPath = route.parent.url[route.parent.url.length - 1].path;
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
const baseUrl = state.url.substring(0, state.url.lastIndexOf(baseUrlSegmentPath) + baseUrlSegmentPath.length);
|
||||||
*/
|
|
||||||
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
|
|
||||||
* @returns BreadcrumbConfig object
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): BreadcrumbConfig<string> {
|
|
||||||
this.getParentRoutes(route);
|
|
||||||
const relatedRoutes = route.data.relatedRoutes;
|
|
||||||
const parentPaths = this.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);
|
|
||||||
|
|
||||||
|
|
||||||
const combinedParentBreadcrumbKeys = relatedParentRoutes.reduce((previous, current) => {
|
const combinedParentBreadcrumbKeys = relatedParentRoutes.reduce((previous, current) => {
|
||||||
return `${previous}:${current.data.breadcrumbKey}`;
|
return `${previous}:${current.data.breadcrumbKey}`;
|
||||||
}, route.data.breadcrumbKey);
|
}, route.data.breadcrumbKey);
|
||||||
const combinedUrls = relatedParentRoutes.reduce((previous, current) => {
|
const combinedUrls = relatedParentRoutes.reduce((previous, current) => {
|
||||||
return `${previous}:${baseUrl}${current.path}`;
|
return `${previous}:${baseUrl}${current.path}`;
|
||||||
}, state.url);
|
}, 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('PublicationClaimBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: PublicationClaimBreadcrumbResolver;
|
let resolver: any;
|
||||||
let publicationClaimBreadcrumbService: any;
|
let publicationClaimBreadcrumbService: any;
|
||||||
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
const fullPath = '/test/publication-claim/openaire:6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||||
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
const expectedKey = '6bee076d-4f2a-4555-a475-04a267769b2a';
|
||||||
@@ -19,11 +19,11 @@ describe('PublicationClaimBreadcrumbResolver', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
publicationClaimBreadcrumbService = {};
|
publicationClaimBreadcrumbService = {};
|
||||||
resolver = new PublicationClaimBreadcrumbResolver(publicationClaimBreadcrumbService);
|
resolver = PublicationClaimBreadcrumbResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the breadcrumb config', () => {
|
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 };
|
const expectedConfig = { provider: publicationClaimBreadcrumbService, key: expectedKey };
|
||||||
expect(resolvedConfig).toEqual(expectedConfig);
|
expect(resolvedConfig).toEqual(expectedConfig);
|
||||||
});
|
});
|
||||||
|
@@ -1,28 +1,18 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
import { PublicationClaimBreadcrumbService } from './publication-claim-breadcrumb.service';
|
||||||
|
|
||||||
@Injectable({
|
export const PublicationClaimBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class PublicationClaimBreadcrumbResolver {
|
breadcrumbService: PublicationClaimBreadcrumbService = inject(PublicationClaimBreadcrumbService),
|
||||||
constructor(protected breadcrumbService: PublicationClaimBreadcrumbService) {
|
): BreadcrumbConfig<string> => {
|
||||||
}
|
const targetId = route.paramMap.get('targetId').split(':')[1];
|
||||||
|
return { provider: breadcrumbService, key: targetId };
|
||||||
/**
|
};
|
||||||
* 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> {
|
|
||||||
const targetId = route.paramMap.get('targetId').split(':')[1];
|
|
||||||
return { provider: this.breadcrumbService, key: targetId };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -2,7 +2,7 @@ import { QualityAssuranceBreadcrumbResolver } from './quality-assurance-breadcru
|
|||||||
|
|
||||||
describe('QualityAssuranceBreadcrumbResolver', () => {
|
describe('QualityAssuranceBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: QualityAssuranceBreadcrumbResolver;
|
let resolver: any;
|
||||||
let qualityAssuranceBreadcrumbService: any;
|
let qualityAssuranceBreadcrumbService: any;
|
||||||
let route: any;
|
let route: any;
|
||||||
const fullPath = '/test/quality-assurance/';
|
const fullPath = '/test/quality-assurance/';
|
||||||
@@ -19,11 +19,11 @@ describe('QualityAssuranceBreadcrumbResolver', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
qualityAssuranceBreadcrumbService = {};
|
qualityAssuranceBreadcrumbService = {};
|
||||||
resolver = new QualityAssuranceBreadcrumbResolver(qualityAssuranceBreadcrumbService);
|
resolver = QualityAssuranceBreadcrumbResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the breadcrumb config', () => {
|
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 };
|
const expectedConfig = { provider: qualityAssuranceBreadcrumbService, key: expectedKey, url: fullPath };
|
||||||
expect(resolvedConfig).toEqual(expectedConfig);
|
expect(resolvedConfig).toEqual(expectedConfig);
|
||||||
});
|
});
|
||||||
|
@@ -1,36 +1,27 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
|
||||||
import { QualityAssuranceBreadcrumbService } from './quality-assurance-breadcrumb.service';
|
import { QualityAssuranceBreadcrumbService } from './quality-assurance-breadcrumb.service';
|
||||||
|
|
||||||
@Injectable({
|
export const QualityAssuranceBreadcrumbResolver: ResolveFn<BreadcrumbConfig<string>> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class QualityAssuranceBreadcrumbResolver {
|
breadcrumbService: QualityAssuranceBreadcrumbService = inject(QualityAssuranceBreadcrumbService),
|
||||||
constructor(protected breadcrumbService: QualityAssuranceBreadcrumbService) {}
|
): BreadcrumbConfig<string> => {
|
||||||
|
const sourceId = route.paramMap.get('sourceId');
|
||||||
|
const topicId = route.paramMap.get('topicId');
|
||||||
|
let key = sourceId;
|
||||||
|
|
||||||
/**
|
if (topicId) {
|
||||||
* Method that resolve QA item into a breadcrumb
|
key += `:${topicId}`;
|
||||||
* 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> {
|
|
||||||
const sourceId = route.paramMap.get('sourceId');
|
|
||||||
const topicId = route.paramMap.get('topicId');
|
|
||||||
let key = sourceId;
|
|
||||||
|
|
||||||
if (topicId) {
|
|
||||||
key += `:${topicId}`;
|
|
||||||
}
|
|
||||||
const fullPath = state.url;
|
|
||||||
const url = fullPath.substr(0, fullPath.indexOf(sourceId));
|
|
||||||
|
|
||||||
return { provider: this.breadcrumbService, key, url };
|
|
||||||
}
|
}
|
||||||
}
|
const fullPath = state.url;
|
||||||
|
const url = fullPath.substring(0, fullPath.indexOf(sourceId));
|
||||||
|
|
||||||
|
return { provider: breadcrumbService, key, url };
|
||||||
|
};
|
||||||
|
@@ -1,45 +1,37 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
|
import { Item } from '../../shared/item.model';
|
||||||
import { getFirstCompletedRemoteData } from '../../shared/operators';
|
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
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, dataService: IdentifiableDataService<SubmissionObject>) => Observable<RemoteData<Item>> = (
|
||||||
export class SubmissionObjectResolver<T> {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
protected dataService: IdentifiableDataService<any>,
|
dataService: IdentifiableDataService<SubmissionObject>,
|
||||||
protected store: Store<any>,
|
): Observable<RemoteData<Item>> => {
|
||||||
) {
|
return dataService.findById(route.params.id,
|
||||||
}
|
true,
|
||||||
|
false,
|
||||||
/**
|
followLink('item'),
|
||||||
* Method for resolving an item based on the parameters in the current route
|
).pipe(
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
getFirstCompletedRemoteData(),
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
|
||||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
getFirstCompletedRemoteData(),
|
||||||
* or an error if something went wrong
|
);
|
||||||
*/
|
};
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<T>> {
|
|
||||||
const itemRD$ = this.dataService.findById(route.params.id,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
followLink('item'),
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<T>>),
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
return itemRD$;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -9,21 +10,10 @@ import { take } from 'rxjs/operators';
|
|||||||
import { SiteDataService } from '../core/data/site-data.service';
|
import { SiteDataService } from '../core/data/site-data.service';
|
||||||
import { Site } from '../core/shared/site.model';
|
import { Site } from '../core/shared/site.model';
|
||||||
|
|
||||||
/**
|
export const HomePageResolver: ResolveFn<Site> = (
|
||||||
* The class that resolve the Site object for a route
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
siteService: SiteDataService = inject(SiteDataService),
|
||||||
export class HomePageResolver {
|
): Observable<Site> => {
|
||||||
constructor(private siteService: SiteDataService) {
|
return siteService.find().pipe(take(1));
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -19,7 +19,7 @@ describe('ItemPageResolver', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: ItemPageResolver;
|
let resolver: any;
|
||||||
let itemService: any;
|
let itemService: any;
|
||||||
let store: any;
|
let store: any;
|
||||||
let router: any;
|
let router: any;
|
||||||
@@ -42,15 +42,19 @@ describe('ItemPageResolver', () => {
|
|||||||
store = jasmine.createSpyObj('store', {
|
store = jasmine.createSpyObj('store', {
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
});
|
});
|
||||||
resolver = new ItemPageResolver(itemService, store, router);
|
resolver = ItemPageResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should redirect to the correct route for the entity type', (done) => {
|
it('should redirect to the correct route for the entity type', (done) => {
|
||||||
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
||||||
spyOn(router, 'navigateByUrl').and.callThrough();
|
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||||
|
|
||||||
resolver.resolve({ params: { id: uuid } } as any, { url: router.parseUrl(`/items/${uuid}`).toString() } as any)
|
resolver({ params: { id: uuid } } as any,
|
||||||
.pipe(first())
|
{ url: router.parseUrl(`/items/${uuid}`).toString() } as any,
|
||||||
|
router,
|
||||||
|
itemService,
|
||||||
|
store,
|
||||||
|
).pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl(`/entities/${entityType}/${uuid}`).toString());
|
expect(router.navigateByUrl).toHaveBeenCalledWith(router.parseUrl(`/entities/${entityType}/${uuid}`).toString());
|
||||||
@@ -63,8 +67,13 @@ describe('ItemPageResolver', () => {
|
|||||||
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
spyOn(item, 'firstMetadataValue').and.returnValue(entityType);
|
||||||
spyOn(router, 'navigateByUrl').and.callThrough();
|
spyOn(router, 'navigateByUrl').and.callThrough();
|
||||||
|
|
||||||
resolver.resolve({ params: { id: uuid } } as any, { url: router.parseUrl(`/entities/${entityType}/${uuid}`).toString() } as any)
|
resolver(
|
||||||
.pipe(first())
|
{ params: { id: uuid } } as any,
|
||||||
|
{ url: router.parseUrl(`/entities/${entityType}/${uuid}`).toString() } as any,
|
||||||
|
router,
|
||||||
|
itemService,
|
||||||
|
store,
|
||||||
|
).pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
expect(router.navigateByUrl).not.toHaveBeenCalled();
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
@@ -8,54 +9,64 @@ import { Store } from '@ngrx/store';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
import { ItemDataService } from '../core/data/item-data.service';
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { hasValue } from '../shared/empty.util';
|
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';
|
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
|
* Method for resolving an item based on the parameters in the current route
|
||||||
* entity page
|
* @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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ItemPageResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
export class ItemPageResolver extends ItemResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
protected itemService: ItemDataService,
|
router: Router = inject(Router),
|
||||||
protected store: Store<any>,
|
itemService: ItemDataService = inject(ItemDataService),
|
||||||
protected router: Router,
|
store: Store<AppState> = inject(Store<AppState>),
|
||||||
) {
|
): Observable<RemoteData<Item>> => {
|
||||||
super(itemService, store, router);
|
const itemRD$ = itemService.findById(
|
||||||
}
|
route.params.id,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
||||||
|
).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||||
* Method for resolving an item based on the parameters in the current route
|
store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||||
* @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>> {
|
|
||||||
return super.resolve(route, state).pipe(
|
|
||||||
map((rd: RemoteData<Item>) => {
|
|
||||||
if (rd.hasSucceeded && hasValue(rd.payload)) {
|
|
||||||
const thisRoute = state.url;
|
|
||||||
|
|
||||||
// Angular uses a custom function for encodeURIComponent, (e.g. it doesn't encode commas
|
return itemRD$.pipe(
|
||||||
// or semicolons) and thisRoute has been encoded with that function. If we want to compare
|
map((rd: RemoteData<Item>) => {
|
||||||
// it with itemRoute, we have to run itemRoute through Angular's version as well to ensure
|
if (rd.hasSucceeded && hasValue(rd.payload)) {
|
||||||
// the same characters are encoded the same way.
|
const thisRoute = state.url;
|
||||||
const itemRoute = this.router.parseUrl(getItemPageRoute(rd.payload)).toString();
|
|
||||||
|
|
||||||
if (!thisRoute.startsWith(itemRoute)) {
|
// Angular uses a custom function for encodeURIComponent, (e.g. it doesn't encode commas
|
||||||
const itemId = rd.payload.uuid;
|
// or semicolons) and thisRoute has been encoded with that function. If we want to compare
|
||||||
const subRoute = thisRoute.substring(thisRoute.indexOf(itemId) + itemId.length, thisRoute.length);
|
// it with itemRoute, we have to run itemRoute through Angular's version as well to ensure
|
||||||
this.router.navigateByUrl(itemRoute + subRoute);
|
// the same characters are encoded the same way.
|
||||||
}
|
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);
|
||||||
|
router.navigateByUrl(itemRoute + subRoute);
|
||||||
}
|
}
|
||||||
return rd;
|
}
|
||||||
}),
|
return rd;
|
||||||
);
|
}),
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
@@ -1,12 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
Router,
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
import { ItemDataService } from '../core/data/item-data.service';
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||||
@@ -31,38 +32,24 @@ export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
|||||||
followLink('thumbnail'),
|
followLink('thumbnail'),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
export const ItemResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
itemService: ItemDataService = inject(ItemDataService),
|
||||||
export class ItemResolver {
|
store: Store<AppState> = inject(Store<AppState>),
|
||||||
constructor(
|
): Observable<RemoteData<Item>> => {
|
||||||
protected itemService: ItemDataService,
|
const itemRD$ = itemService.findById(
|
||||||
protected store: Store<any>,
|
route.params.id,
|
||||||
protected router: Router,
|
true,
|
||||||
) {
|
false,
|
||||||
}
|
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
||||||
|
).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||||
* Method for resolving an item based on the parameters in the current route
|
store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||||
* @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,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
...ITEM_PAGE_LINKS_TO_FOLLOW,
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
|
|
||||||
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
return itemRD$;
|
||||||
this.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 {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
Router,
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { VersionDataService } from '../../core/data/version-data.service';
|
import { VersionDataService } from '../../core/data/version-data.service';
|
||||||
import { ResolvedAction } from '../../core/resolving/resolver.actions';
|
import { ResolvedAction } from '../../core/resolving/resolver.actions';
|
||||||
@@ -26,37 +27,31 @@ export const VERSION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Version>[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific version before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const VersionResolver: ResolveFn<RemoteData<Version>> = (
|
||||||
export class VersionResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
protected versionService: VersionDataService,
|
versionService: VersionDataService = inject(VersionDataService),
|
||||||
protected store: Store<any>,
|
store: Store<AppState> = inject(Store<AppState>),
|
||||||
protected router: Router,
|
): Observable<RemoteData<Version>> => {
|
||||||
) {
|
const versionRD$ = versionService.findById(route.params.id,
|
||||||
}
|
true,
|
||||||
|
false,
|
||||||
|
...VERSION_PAGE_LINKS_TO_FOLLOW,
|
||||||
|
).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
versionRD$.subscribe((versionRD: RemoteData<Version>) => {
|
||||||
* Method for resolving a version based on the parameters in the current route
|
store.dispatch(new ResolvedAction(state.url, versionRD.payload));
|
||||||
* @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<Version>> {
|
|
||||||
const versionRD$ = this.versionService.findById(route.params.id,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
...VERSION_PAGE_LINKS_TO_FOLLOW,
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
|
|
||||||
versionRD$.subscribe((versionRD: RemoteData<Version>) => {
|
return versionRD$;
|
||||||
this.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 { AuthorizationDataService } from './core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
import { FeatureID } from './core/data/feature-authorization/feature-id';
|
||||||
import { ScriptDataService } from './core/data/processes/script-data.service';
|
import { ScriptDataService } from './core/data/processes/script-data.service';
|
||||||
import { MenuResolver } from './menu.resolver';
|
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
import { MenuService } from './shared/menu/menu.service';
|
||||||
import { MenuID } from './shared/menu/menu-id.model';
|
import { MenuID } from './shared/menu/menu-id.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from './shared/remote-data.utils';
|
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 { MenuServiceStub } from './shared/testing/menu-service.stub';
|
||||||
import { createPaginatedList } from './shared/testing/utils.test';
|
import { createPaginatedList } from './shared/testing/utils.test';
|
||||||
import createSpy = jasmine.createSpy;
|
import createSpy = jasmine.createSpy;
|
||||||
|
import { MenuResolverService } from './menu-resolver.service';
|
||||||
|
|
||||||
const BOOLEAN = { t: true, f: false };
|
const BOOLEAN = { t: true, f: false };
|
||||||
const MENU_STATE = {
|
const MENU_STATE = {
|
||||||
@@ -39,7 +39,7 @@ const BROWSE_DEFINITIONS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
describe('MenuResolver', () => {
|
describe('MenuResolver', () => {
|
||||||
let resolver: MenuResolver;
|
let resolver: MenuResolverService;
|
||||||
|
|
||||||
let menuService;
|
let menuService;
|
||||||
let browseService;
|
let browseService;
|
||||||
@@ -79,12 +79,12 @@ describe('MenuResolver', () => {
|
|||||||
{ provide: AuthorizationDataService, useValue: authorizationService },
|
{ provide: AuthorizationDataService, useValue: authorizationService },
|
||||||
{ provide: ScriptDataService, useValue: scriptService },
|
{ provide: ScriptDataService, useValue: scriptService },
|
||||||
{ provide: ConfigurationDataService, useValue: configurationDataService },
|
{ provide: ConfigurationDataService, useValue: configurationDataService },
|
||||||
{
|
{ provide: NgbModal, useValue: mockNgbModal },
|
||||||
provide: NgbModal, useValue: mockNgbModal },
|
MenuResolverService,
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
});
|
});
|
||||||
resolver = TestBed.inject(MenuResolver);
|
resolver = TestBed.inject(MenuResolverService);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should be created', () => {
|
it('should be created', () => {
|
||||||
|
@@ -1,846 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { Observable } from 'rxjs';
|
||||||
import {
|
|
||||||
combineLatest,
|
import { MenuResolverService } from './menu-resolver.service';
|
||||||
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
|
* Initialize all menus
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const MenuResolver: ResolveFn<boolean> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class MenuResolver {
|
menuResolverService: MenuResolverService = inject(MenuResolverService),
|
||||||
constructor(
|
): Observable<boolean> => {
|
||||||
protected menuService: MenuService,
|
return menuResolverService.resolve(route, state);
|
||||||
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,
|
|
||||||
})));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -5,7 +5,7 @@ import { Process } from './processes/process.model';
|
|||||||
|
|
||||||
describe('ProcessBreadcrumbResolver', () => {
|
describe('ProcessBreadcrumbResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: ProcessBreadcrumbResolver;
|
let resolver: any;
|
||||||
let processDataService: ProcessDataService;
|
let processDataService: ProcessDataService;
|
||||||
let processBreadcrumbService: any;
|
let processBreadcrumbService: any;
|
||||||
let process: Process;
|
let process: Process;
|
||||||
@@ -19,11 +19,16 @@ describe('ProcessBreadcrumbResolver', () => {
|
|||||||
processDataService = {
|
processDataService = {
|
||||||
findById: () => createSuccessfulRemoteDataObject$(process),
|
findById: () => createSuccessfulRemoteDataObject$(process),
|
||||||
} as any;
|
} as any;
|
||||||
resolver = new ProcessBreadcrumbResolver(processBreadcrumbService, processDataService);
|
resolver = ProcessBreadcrumbResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve the breadcrumb config', (done) => {
|
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 };
|
const expectedConfig = { provider: processBreadcrumbService, key: process, url: path };
|
||||||
resolvedConfig.subscribe((config) => {
|
resolvedConfig.subscribe((config) => {
|
||||||
expect(config).toEqual(expectedConfig);
|
expect(config).toEqual(expectedConfig);
|
||||||
@@ -33,7 +38,7 @@ describe('ProcessBreadcrumbResolver', () => {
|
|||||||
|
|
||||||
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
it('should resolve throw an error when no breadcrumbKey is defined', () => {
|
||||||
expect(() => {
|
expect(() => {
|
||||||
resolver.resolve({ data: {} } as any, undefined);
|
resolver({ data: {} } as any, undefined, processBreadcrumbService, processDataService);
|
||||||
}).toThrow();
|
}).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -15,30 +16,28 @@ import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
|
|||||||
import { Process } from './processes/process.model';
|
import { Process } from './processes/process.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific process before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ProcessBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Process>> = (
|
||||||
export class ProcessBreadcrumbResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(protected breadcrumbService: ProcessBreadcrumbsService, private processService: ProcessDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
breadcrumbService: ProcessBreadcrumbsService = inject(ProcessBreadcrumbsService),
|
||||||
|
processService: ProcessDataService = inject(ProcessDataService),
|
||||||
|
): Observable<BreadcrumbConfig<Process>> => {
|
||||||
|
const id = route.params.id;
|
||||||
|
|
||||||
/**
|
return processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
||||||
* Method for resolving a process based on the parameters in the current route
|
getFirstCompletedRemoteData(),
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
map((object: RemoteData<Process>) => {
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
const fullPath = state.url;
|
||||||
* @returns Observable<<RemoteData<Process>> Emits the found process based on the parameters in the current route,
|
const url = fullPath.substring(0, fullPath.indexOf(id)).concat(id);
|
||||||
* or an error if something went wrong
|
return { provider: breadcrumbService, key: object.payload, url: url };
|
||||||
*/
|
}),
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Process>> {
|
);
|
||||||
const id = route.params.id;
|
};
|
||||||
|
|
||||||
return this.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 };
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
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
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ProcessPageResolver: ResolveFn<RemoteData<Process>> = (
|
||||||
export class ProcessPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(private processService: ProcessDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
processService: ProcessDataService = inject(ProcessDataService),
|
||||||
|
): Observable<RemoteData<Process>> => {
|
||||||
/**
|
return processService.findById(route.params.id, false, true, ...PROCESS_PAGE_FOLLOW_LINKS).pipe(
|
||||||
* Method for resolving a process based on the parameters in the current route
|
getFirstCompletedRemoteData(),
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
);
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
};
|
||||||
* @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(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} 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.
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ItemResolver: ResolveFn<AssuranceEventsPageParams> = (
|
||||||
export class QualityAssuranceEventsPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot,
|
||||||
/**
|
): AssuranceEventsPageParams => {
|
||||||
* Method for resolving the parameters in the current route.
|
return {
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
pageId: route.queryParams.pageId,
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
pageSize: parseInt(route.queryParams.pageSize, 10),
|
||||||
* @returns AdminQualityAssuranceEventsPageParams Emits the route parameters
|
currentPage: parseInt(route.queryParams.page, 10),
|
||||||
*/
|
};
|
||||||
resolve(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 {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
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 { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { QualityAssuranceSourceObject } from '../../core/notifications/qa/models/quality-assurance-source.model';
|
import { QualityAssuranceSourceObject } from '../../core/notifications/qa/models/quality-assurance-source.model';
|
||||||
import { QualityAssuranceSourceService } from '../../notifications/qa/source/quality-assurance-source.service';
|
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.
|
* 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[]>
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const SourceDataResolver: ResolveFn<QualityAssuranceSourceObject[]> = (
|
||||||
export class SourceDataResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
private pageSize = environment.qualityAssuranceConfig.pageSize;
|
state: RouterStateSnapshot,
|
||||||
/**
|
router: Router = inject(Router),
|
||||||
* Initialize the effect class variables.
|
qualityAssuranceSourceService: QualityAssuranceSourceService = inject(QualityAssuranceSourceService),
|
||||||
* @param {QualityAssuranceSourceService} qualityAssuranceSourceService
|
appConfig: AppConfig = inject(APP_CONFIG),
|
||||||
*/
|
): Observable<QualityAssuranceSourceObject[]> => {
|
||||||
constructor(
|
const pageSize = appConfig.qualityAssuranceConfig.pageSize;
|
||||||
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
|
|
||||||
* @returns Observable<QualityAssuranceSourceObject[]>
|
|
||||||
*/
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<QualityAssuranceSourceObject[]> {
|
|
||||||
return this.qualityAssuranceSourceService.getSources(this.pageSize, 0).pipe(
|
|
||||||
map((sources: PaginatedList<QualityAssuranceSourceObject>) => {
|
|
||||||
if (sources.page.length === 1) {
|
|
||||||
this.router.navigate([this.getResolvedUrl(route) + '/' + sources.page[0].id]);
|
|
||||||
}
|
|
||||||
return sources.page;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return qualityAssuranceSourceService.getSources(pageSize, 0).pipe(
|
||||||
*
|
map((sources: PaginatedList<QualityAssuranceSourceObject>) => {
|
||||||
* @param route url path
|
if (sources.page.length === 1) {
|
||||||
* @returns url path
|
router.navigate([getResolvedUrl(route) + '/' + sources.page[0].id]);
|
||||||
*/
|
}
|
||||||
getResolvedUrl(route: ActivatedRouteSnapshot): string {
|
return sources.page;
|
||||||
return route.pathFromRoot.map(v => v.url.map(segment => segment.toString()).join('/')).join('/');
|
}));
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param route url path
|
||||||
|
* @returns url path
|
||||||
|
*/
|
||||||
|
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';
|
import { RegistrationResolver } from './registration.resolver';
|
||||||
|
|
||||||
describe('RegistrationResolver', () => {
|
describe('RegistrationResolver', () => {
|
||||||
let resolver: RegistrationResolver;
|
let resolver: any;
|
||||||
let epersonRegistrationService: EpersonRegistrationService;
|
let epersonRegistrationService: EpersonRegistrationService;
|
||||||
|
|
||||||
const token = 'test-token';
|
const token = 'test-token';
|
||||||
@@ -16,11 +16,11 @@ describe('RegistrationResolver', () => {
|
|||||||
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
epersonRegistrationService = jasmine.createSpyObj('epersonRegistrationService', {
|
||||||
searchByToken: createSuccessfulRemoteDataObject$(registration),
|
searchByToken: createSuccessfulRemoteDataObject$(registration),
|
||||||
});
|
});
|
||||||
resolver = new RegistrationResolver(epersonRegistrationService);
|
resolver = RegistrationResolver;
|
||||||
});
|
});
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
it('should resolve a registration based on the token', (done) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -10,19 +11,13 @@ import { RemoteData } from '../core/data/remote-data';
|
|||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { Registration } from '../core/shared/registration.model';
|
import { Registration } from '../core/shared/registration.model';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
export const RegistrationResolver: ResolveFn<RemoteData<Registration>> = (
|
||||||
/**
|
route: ActivatedRouteSnapshot,
|
||||||
* Resolver to resolve a Registration object based on the provided token
|
state: RouterStateSnapshot,
|
||||||
*/
|
epersonRegistrationService: EpersonRegistrationService = inject(EpersonRegistrationService),
|
||||||
export class RegistrationResolver {
|
): Observable<RemoteData<Registration>> => {
|
||||||
|
const token = route.params.token;
|
||||||
constructor(private epersonRegistrationService: EpersonRegistrationService) {
|
return epersonRegistrationService.searchByToken(token).pipe(
|
||||||
}
|
getFirstCompletedRemoteData(),
|
||||||
|
);
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Registration>> {
|
};
|
||||||
const token = route.params.token;
|
|
||||||
return this.epersonRegistrationService.searchByToken(token).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -10,21 +11,12 @@ import { RemoteData } from '../core/data/remote-data';
|
|||||||
import { ItemRequest } from '../core/shared/item-request.model';
|
import { ItemRequest } from '../core/shared/item-request.model';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
|
|
||||||
/**
|
export const RequestCopyResolver: ResolveFn<RemoteData<ItemRequest>> = (
|
||||||
* Resolves an {@link ItemRequest} from the token found in the route's parameters
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
itemRequestDataService: ItemRequestDataService = inject(ItemRequestDataService),
|
||||||
export class RequestCopyResolver {
|
): Observable<RemoteData<ItemRequest>> => {
|
||||||
|
return itemRequestDataService.findById(route.params.token).pipe(
|
||||||
constructor(
|
getFirstCompletedRemoteData(),
|
||||||
private itemRequestDataService: ItemRequestDataService,
|
);
|
||||||
) {
|
};
|
||||||
}
|
|
||||||
|
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<ItemRequest>> | Promise<RemoteData<ItemRequest>> | RemoteData<ItemRequest> {
|
|
||||||
return this.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';
|
} from '../remote-data.utils';
|
||||||
import { MenuServiceStub } from '../testing/menu-service.stub';
|
import { MenuServiceStub } from '../testing/menu-service.stub';
|
||||||
import { createPaginatedList } from '../testing/utils.test';
|
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 { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
|
||||||
import { DsoWithdrawnReinstateModalService } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
import { DsoWithdrawnReinstateModalService } from './dso-withdrawn-reinstate-service/dso-withdrawn-reinstate-modal.service';
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
id: 'some menu',
|
id: 'some menu',
|
||||||
};
|
};
|
||||||
|
|
||||||
let resolver: DSOEditMenuResolver;
|
let resolver: DSOEditMenuResolverService;
|
||||||
|
|
||||||
let dSpaceObjectDataService;
|
let dSpaceObjectDataService;
|
||||||
let menuService;
|
let menuService;
|
||||||
@@ -189,12 +189,12 @@ describe('DSOEditMenuResolver', () => {
|
|||||||
{ provide: NotificationsService, useValue: notificationsService },
|
{ provide: NotificationsService, useValue: notificationsService },
|
||||||
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
{ provide: DsoWithdrawnReinstateModalService, useValue: dsoWithdrawnReinstateModalService },
|
||||||
{ provide: CorrectionTypeDataService, useValue: correctionsDataService },
|
{ provide: CorrectionTypeDataService, useValue: correctionsDataService },
|
||||||
{
|
{ provide: NgbModal, useValue: mockNgbModal },
|
||||||
provide: NgbModal, useValue: mockNgbModal },
|
DSOEditMenuResolverService,
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
});
|
});
|
||||||
resolver = TestBed.inject(DSOEditMenuResolver);
|
resolver = TestBed.inject(DSOEditMenuResolverService);
|
||||||
|
|
||||||
spyOn(menuService, 'addSection');
|
spyOn(menuService, 'addSection');
|
||||||
}));
|
}));
|
||||||
|
@@ -1,324 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { Observable } from 'rxjs';
|
||||||
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 { MenuSection } from '../menu/menu-section.model';
|
||||||
import { NotificationsService } from '../notifications/notifications.service';
|
import { DSOEditMenuResolverService } from './dso-edit-menu-resolver.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
|
* Initialise all dspace object related menus
|
||||||
*/
|
*/
|
||||||
@Injectable({
|
export const DSOEditMenuResolver: ResolveFn<{ [key: string]: MenuSection[] }> = (
|
||||||
providedIn: 'root',
|
route: ActivatedRouteSnapshot,
|
||||||
})
|
state: RouterStateSnapshot,
|
||||||
export class DSOEditMenuResolver {
|
menuResolverService: DSOEditMenuResolverService = inject(DSOEditMenuResolverService),
|
||||||
|
): Observable<{ [key: string]: MenuSection[] }> => {
|
||||||
constructor(
|
return menuResolverService.resolve(route, state);
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -53,6 +53,7 @@ import {
|
|||||||
DynamicFormValidationService,
|
DynamicFormValidationService,
|
||||||
DynamicTemplateDirective,
|
DynamicTemplateDirective,
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
import { DynamicFormControlMapFn } from '@ng-dynamic-forms/core/lib/service/dynamic-form-component.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
@@ -74,7 +75,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
APP_CONFIG,
|
APP_CONFIG,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
DynamicFormControlFn,
|
|
||||||
} from '../../../../../config/app-config.interface';
|
} from '../../../../../config/app-config.interface';
|
||||||
import { AppState } from '../../../../app.reducer';
|
import { AppState } from '../../../../app.reducer';
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
@@ -214,7 +214,7 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
|
|||||||
public formBuilderService: FormBuilderService,
|
public formBuilderService: FormBuilderService,
|
||||||
private submissionService: SubmissionService,
|
private submissionService: SubmissionService,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
@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);
|
super(ref, componentFactoryResolver, layoutService, validationService, dynamicFormComponentService, relationService);
|
||||||
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
|
this.fetchThumbnail = this.appConfig.browseBy.showThumbnails;
|
||||||
|
@@ -1,21 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
Inject,
|
inject,
|
||||||
Injectable,
|
|
||||||
InjectionToken,
|
InjectionToken,
|
||||||
Injector,
|
Injector,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
import {
|
import { LazyDataServicesMap } from '../../../../config/app-config.interface';
|
||||||
APP_DATA_SERVICES_MAP,
|
|
||||||
LazyDataServicesMap,
|
|
||||||
} from '../../../../config/app-config.interface';
|
|
||||||
import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../../../core/data/base/identifiable-data.service';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { lazyService } from '../../../core/lazy-service';
|
import { lazyService } from '../../../core/lazy-service';
|
||||||
@@ -25,40 +22,36 @@ import { ResourceType } from '../../../core/shared/resource-type';
|
|||||||
import { isEmpty } from '../../empty.util';
|
import { isEmpty } from '../../empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ResourcePolicyTargetResolver: ResolveFn<RemoteData<DSpaceObject>> = (
|
||||||
export class ResourcePolicyTargetResolver {
|
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');
|
||||||
|
|
||||||
constructor(
|
if (isEmpty(targetType) || isEmpty(policyTargetId)) {
|
||||||
private parentInjector: Injector,
|
router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
private router: Router,
|
|
||||||
@Inject(APP_DATA_SERVICES_MAP) private dataServiceMap: InjectionToken<LazyDataServicesMap>) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const resourceType: ResourceType = new ResourceType(targetType);
|
||||||
* Method for resolving an item based on the parameters in the current route
|
const lazyProvider$: Observable<IdentifiableDataService<DSpaceObject>> = lazyService(dataServiceMap[resourceType.value], parentInjector);
|
||||||
* @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<DSpaceObject>> {
|
|
||||||
const targetType = route.queryParamMap.get('targetType');
|
|
||||||
const policyTargetId = route.queryParamMap.get('policyTargetId');
|
|
||||||
|
|
||||||
if (isEmpty(targetType) || isEmpty(policyTargetId)) {
|
return lazyProvider$.pipe(
|
||||||
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
switchMap((dataService: IdentifiableDataService<DSpaceObject>) => {
|
||||||
}
|
return dataService.findById(policyTargetId);
|
||||||
|
}),
|
||||||
const resourceType: ResourceType = new ResourceType(targetType);
|
getFirstCompletedRemoteData(),
|
||||||
const lazyProvider$: Observable<IdentifiableDataService<DSpaceObject>> = lazyService(this.dataServiceMap[resourceType.value], this.parentInjector);
|
);
|
||||||
|
};
|
||||||
return lazyProvider$.pipe(
|
|
||||||
switchMap((dataService: IdentifiableDataService<DSpaceObject>) => {
|
|
||||||
return dataService.findById(policyTargetId);
|
|
||||||
}),
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
Router,
|
Router,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
@@ -14,30 +15,27 @@ import { isEmpty } from '../../empty.util';
|
|||||||
import { followLink } from '../../utils/follow-link-config.model';
|
import { followLink } from '../../utils/follow-link-config.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const ResourcePolicyResolver: ResolveFn<RemoteData<ResourcePolicy>> = (
|
||||||
export class ResourcePolicyResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot,
|
||||||
|
router: Router = inject(Router),
|
||||||
|
resourcePolicyService: ResourcePolicyDataService = inject(ResourcePolicyDataService),
|
||||||
|
): Observable<RemoteData<ResourcePolicy>> => {
|
||||||
|
const policyId = route.queryParamMap.get('policyId');
|
||||||
|
|
||||||
constructor(private resourcePolicyService: ResourcePolicyDataService, private router: Router) {
|
if (isEmpty(policyId)) {
|
||||||
|
router.navigateByUrl('/404', { skipLocationChange: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return resourcePolicyService.findById(policyId, true, false, followLink('eperson'), followLink('group')).pipe(
|
||||||
* Method for resolving an item based on the parameters in the current route
|
getFirstCompletedRemoteData(),
|
||||||
* @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<ResourcePolicy>> {
|
|
||||||
const policyId = route.queryParamMap.get('policyId');
|
|
||||||
|
|
||||||
if (isEmpty(policyId)) {
|
|
||||||
this.router.navigateByUrl('/404', { skipLocationChange: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.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 {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -12,23 +13,19 @@ import { SuggestionTargetDataService } from '../core/notifications/target/sugges
|
|||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific collection before the route is activated
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const SuggestionsPageResolver: ResolveFn<RemoteData<SuggestionTarget>> = (
|
||||||
export class SuggestionsPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(private suggestionsDataService: SuggestionTargetDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
suggestionsDataService: SuggestionTargetDataService = inject(SuggestionTargetDataService),
|
||||||
|
): Observable<RemoteData<SuggestionTarget>> => {
|
||||||
/**
|
return suggestionsDataService.getTargetById(route.params.targetId).pipe(
|
||||||
* Method for resolving a suggestion target based on the parameters in the current route
|
find((RD) => hasValue(RD.hasFailed) || RD.hasSucceeded),
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
);
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
};
|
||||||
* @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(
|
|
||||||
find((RD) => hasValue(RD.hasFailed) || RD.hasSucceeded),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -6,7 +6,7 @@ import { ItemFromWorkflowResolver } from './item-from-workflow.resolver';
|
|||||||
|
|
||||||
describe('ItemFromWorkflowResolver', () => {
|
describe('ItemFromWorkflowResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: ItemFromWorkflowResolver;
|
let resolver: any;
|
||||||
let wfiService: WorkflowItemDataService;
|
let wfiService: WorkflowItemDataService;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
const itemUuid = '8888-8888-8888-8888';
|
const itemUuid = '8888-8888-8888-8888';
|
||||||
@@ -20,11 +20,11 @@ describe('ItemFromWorkflowResolver', () => {
|
|||||||
wfiService = {
|
wfiService = {
|
||||||
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
||||||
} as any;
|
} 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) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,20 +1,21 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
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 { Item } from '../core/shared/item.model';
|
||||||
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
||||||
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
||||||
|
|
||||||
/**
|
export const ItemFromWorkflowResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
workflowItemService: WorkflowItemDataService = inject(WorkflowItemDataService),
|
||||||
export class ItemFromWorkflowResolver extends SubmissionObjectResolver<Item> {
|
): Observable<RemoteData<Item>> => {
|
||||||
constructor(
|
return SubmissionObjectResolver(route, state, workflowItemService);
|
||||||
private workflowItemService: WorkflowItemDataService,
|
};
|
||||||
protected store: Store<any>,
|
|
||||||
) {
|
|
||||||
super(workflowItemService, store);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -6,7 +6,7 @@ import { WorkflowItemPageResolver } from './workflow-item-page.resolver';
|
|||||||
|
|
||||||
describe('WorkflowItemPageResolver', () => {
|
describe('WorkflowItemPageResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: WorkflowItemPageResolver;
|
let resolver: any;
|
||||||
let wfiService: WorkflowItemDataService;
|
let wfiService: WorkflowItemDataService;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
|
|
||||||
@@ -14,11 +14,11 @@ describe('WorkflowItemPageResolver', () => {
|
|||||||
wfiService = {
|
wfiService = {
|
||||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||||
} as any;
|
} as any;
|
||||||
resolver = new WorkflowItemPageResolver(wfiService);
|
resolver = WorkflowItemPageResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve a workflow item with the correct id', (done) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
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 { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||||
|
|
||||||
/**
|
export const WorkflowItemPageResolver: ResolveFn<RemoteData<WorkflowItem>> = (
|
||||||
* This class represents a resolver that requests a specific workflow item before the route is activated
|
route: ActivatedRouteSnapshot,
|
||||||
*/
|
state: RouterStateSnapshot,
|
||||||
@Injectable({ providedIn: 'root' })
|
workflowItemService: WorkflowItemDataService = inject(WorkflowItemDataService),
|
||||||
export class WorkflowItemPageResolver {
|
): Observable<RemoteData<WorkflowItem>> => {
|
||||||
constructor(private workflowItemService: WorkflowItemDataService) {
|
return workflowItemService.findById(
|
||||||
}
|
route.params.id,
|
||||||
|
true,
|
||||||
/**
|
false,
|
||||||
* Method for resolving a workflow item based on the parameters in the current route
|
followLink('item'),
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
).pipe(
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
getFirstCompletedRemoteData(),
|
||||||
* @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,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
followLink('item'),
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -6,7 +6,7 @@ import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
|
|||||||
|
|
||||||
describe('ItemFromWorkspaceResolver', () => {
|
describe('ItemFromWorkspaceResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: ItemFromWorkspaceResolver;
|
let resolver: any;
|
||||||
let wfiService: WorkspaceitemDataService;
|
let wfiService: WorkspaceitemDataService;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
const itemUuid = '8888-8888-8888-8888';
|
const itemUuid = '8888-8888-8888-8888';
|
||||||
@@ -20,11 +20,11 @@ describe('ItemFromWorkspaceResolver', () => {
|
|||||||
wfiService = {
|
wfiService = {
|
||||||
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi),
|
||||||
} as any;
|
} 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) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,20 +1,23 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import { Store } from '@ngrx/store';
|
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 { Item } from '../core/shared/item.model';
|
||||||
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
|
||||||
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
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 const ItemFromWorkspaceResolver: ResolveFn<RemoteData<Item>> = (
|
||||||
export class ItemFromWorkspaceResolver extends SubmissionObjectResolver<Item> {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(
|
state: RouterStateSnapshot,
|
||||||
private workspaceItemService: WorkspaceitemDataService,
|
workspaceItemService: WorkspaceitemDataService = inject(WorkspaceitemDataService),
|
||||||
protected store: Store<any>,
|
): Observable<RemoteData<Item>> => {
|
||||||
) {
|
return SubmissionObjectResolver(route, state, workspaceItemService);
|
||||||
super(workspaceItemService, store);
|
};
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@@ -6,7 +6,7 @@ import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
|
|||||||
|
|
||||||
describe('WorkflowItemPageResolver', () => {
|
describe('WorkflowItemPageResolver', () => {
|
||||||
describe('resolve', () => {
|
describe('resolve', () => {
|
||||||
let resolver: WorkspaceItemPageResolver;
|
let resolver: any;
|
||||||
let wsiService: WorkspaceitemDataService;
|
let wsiService: WorkspaceitemDataService;
|
||||||
const uuid = '1234-65487-12354-1235';
|
const uuid = '1234-65487-12354-1235';
|
||||||
|
|
||||||
@@ -14,11 +14,11 @@ describe('WorkflowItemPageResolver', () => {
|
|||||||
wsiService = {
|
wsiService = {
|
||||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
findById: (id: string) => createSuccessfulRemoteDataObject$({ id }),
|
||||||
} as any;
|
} as any;
|
||||||
resolver = new WorkspaceItemPageResolver(wsiService);
|
resolver = WorkspaceItemPageResolver;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve a workspace item with the correct id', (done) => {
|
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())
|
.pipe(first())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
(resolved) => {
|
(resolved) => {
|
||||||
|
@@ -1,38 +1,35 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
|
ResolveFn,
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
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 { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
|
||||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
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
|
* 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
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
export const WorkspaceItemPageResolver: ResolveFn<RemoteData<WorkspaceItem>> = (
|
||||||
export class WorkspaceItemPageResolver {
|
route: ActivatedRouteSnapshot,
|
||||||
constructor(private workspaceItemService: WorkspaceitemDataService) {
|
state: RouterStateSnapshot,
|
||||||
}
|
workspaceItemService: WorkspaceitemDataService = inject(WorkspaceitemDataService),
|
||||||
|
): Observable<RemoteData<WorkspaceItem>> => {
|
||||||
/**
|
return workspaceItemService.findById(route.params.id,
|
||||||
* Method for resolving a workflow item based on the parameters in the current route
|
true,
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
false,
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
followLink('item'),
|
||||||
* @returns Observable<<RemoteData<Item>> Emits the found workflow item based on the parameters in the current route,
|
).pipe(
|
||||||
* or an error if something went wrong
|
getFirstCompletedRemoteData(),
|
||||||
*/
|
);
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
|
};
|
||||||
return this.workspaceItemService.findById(route.params.id,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
followLink('item'),
|
|
||||||
).pipe(
|
|
||||||
getFirstCompletedRemoteData(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -3,7 +3,6 @@ import {
|
|||||||
makeStateKey,
|
makeStateKey,
|
||||||
Type,
|
Type,
|
||||||
} from '@angular/core';
|
} 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 { 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';
|
import { HALDataService } from '../app/core/data/base/hal-data-service.interface';
|
||||||
@@ -78,11 +77,8 @@ export interface LazyDataServicesMap {
|
|||||||
[type: string]: () => Promise<Type<HALDataService<any>>>
|
[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_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 {
|
export {
|
||||||
APP_CONFIG,
|
APP_CONFIG,
|
||||||
APP_CONFIG_STATE,
|
APP_CONFIG_STATE,
|
||||||
|
Reference in New Issue
Block a user