mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'upstream/main' into w2p-77205_issue-927-Non-site-admin-edit-authorization-group
This commit is contained in:
@@ -11,6 +11,8 @@ import { Collection } from '../../../../../core/shared/collection.model';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { getCollectionEditRoute } from '../../../../../+collection-page/collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../../../../../+collection-page/collection-page-routing-paths';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
describe('CollectionAdminSearchResultListElementComponent', () => {
|
describe('CollectionAdminSearchResultListElementComponent', () => {
|
||||||
let component: CollectionAdminSearchResultListElementComponent;
|
let component: CollectionAdminSearchResultListElementComponent;
|
||||||
@@ -33,7 +35,8 @@ describe('CollectionAdminSearchResultListElementComponent', () => {
|
|||||||
RouterTestingModule.withRoutes([])
|
RouterTestingModule.withRoutes([])
|
||||||
],
|
],
|
||||||
declarations: [CollectionAdminSearchResultListElementComponent],
|
declarations: [CollectionAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -11,6 +11,8 @@ import { CommunityAdminSearchResultListElementComponent } from './community-admi
|
|||||||
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
|
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
|
||||||
import { Community } from '../../../../../core/shared/community.model';
|
import { Community } from '../../../../../core/shared/community.model';
|
||||||
import { getCommunityEditRoute } from '../../../../../+community-page/community-page-routing-paths';
|
import { getCommunityEditRoute } from '../../../../../+community-page/community-page-routing-paths';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
describe('CommunityAdminSearchResultListElementComponent', () => {
|
describe('CommunityAdminSearchResultListElementComponent', () => {
|
||||||
let component: CommunityAdminSearchResultListElementComponent;
|
let component: CommunityAdminSearchResultListElementComponent;
|
||||||
@@ -33,7 +35,8 @@ describe('CommunityAdminSearchResultListElementComponent', () => {
|
|||||||
RouterTestingModule.withRoutes([])
|
RouterTestingModule.withRoutes([])
|
||||||
],
|
],
|
||||||
declarations: [CommunityAdminSearchResultListElementComponent],
|
declarations: [CommunityAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -8,6 +8,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component';
|
import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
describe('ItemAdminSearchResultListElementComponent', () => {
|
describe('ItemAdminSearchResultListElementComponent', () => {
|
||||||
let component: ItemAdminSearchResultListElementComponent;
|
let component: ItemAdminSearchResultListElementComponent;
|
||||||
@@ -30,7 +32,8 @@ describe('ItemAdminSearchResultListElementComponent', () => {
|
|||||||
RouterTestingModule.withRoutes([])
|
RouterTestingModule.withRoutes([])
|
||||||
],
|
],
|
||||||
declarations: [ItemAdminSearchResultListElementComponent],
|
declarations: [ItemAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -16,6 +16,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||||
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
||||||
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
|
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
|
||||||
@@ -49,6 +51,7 @@ describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: LinkService, useValue: linkService },
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -12,6 +12,7 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
|
import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch)
|
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -29,8 +30,11 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S
|
|||||||
*/
|
*/
|
||||||
public item$: Observable<Item>;
|
public item$: Observable<Item>;
|
||||||
|
|
||||||
constructor(private linkService: LinkService, protected truncatableService: TruncatableService) {
|
constructor(private linkService: LinkService,
|
||||||
super(truncatableService);
|
protected truncatableService: TruncatableService,
|
||||||
|
protected dsoNameService: DSONameService
|
||||||
|
) {
|
||||||
|
super(truncatableService, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BrowseBySwitcherComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-browse-by-switcher',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedBrowseBySwitcherComponent extends ThemedComponent<BrowseBySwitcherComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BrowseBySwitcherComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+browse-by/+browse-by-switcher/browse-by-switcher.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./browse-by-switcher.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@@ -1,9 +1,9 @@
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { BrowseBySwitcherComponent } from './+browse-by-switcher/browse-by-switcher.component';
|
|
||||||
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
||||||
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||||
|
import { ThemedBrowseBySwitcherComponent } from './+browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -14,7 +14,7 @@ import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.reso
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: BrowseBySwitcherComponent,
|
component: ThemedBrowseBySwitcherComponent,
|
||||||
canActivate: [BrowseByGuard],
|
canActivate: [BrowseByGuard],
|
||||||
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
||||||
data: { title: 'browse.title', breadcrumbKey: 'browse.metadata' }
|
data: { title: 'browse.title', breadcrumbKey: 'browse.metadata' }
|
||||||
|
@@ -5,6 +5,7 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
|
import { BrowseByMetadataPageComponent } from './+browse-by-metadata-page/browse-by-metadata-page.component';
|
||||||
import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.component';
|
||||||
import { BrowseBySwitcherComponent } from './+browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './+browse-by-switcher/browse-by-switcher.component';
|
||||||
|
import { ThemedBrowseBySwitcherComponent } from './+browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -20,6 +21,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BrowseBySwitcherComponent,
|
BrowseBySwitcherComponent,
|
||||||
|
ThemedBrowseBySwitcherComponent,
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
@@ -24,6 +24,10 @@ export function getCollectionEditRolesRoute(id) {
|
|||||||
return new URLCombiner(getCollectionPageRoute(id), COLLECTION_EDIT_PATH, COLLECTION_EDIT_ROLES_PATH).toString();
|
return new URLCombiner(getCollectionPageRoute(id), COLLECTION_EDIT_PATH, COLLECTION_EDIT_ROLES_PATH).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCollectionItemTemplateRoute(id) {
|
||||||
|
return new URLCombiner(getCollectionPageRoute(id), ITEMTEMPLATE_PATH).toString();
|
||||||
|
}
|
||||||
|
|
||||||
export const COLLECTION_CREATE_PATH = 'create';
|
export const COLLECTION_CREATE_PATH = 'create';
|
||||||
export const COLLECTION_EDIT_PATH = 'edit';
|
export const COLLECTION_EDIT_PATH = 'edit';
|
||||||
export const COLLECTION_EDIT_ROLES_PATH = 'roles';
|
export const COLLECTION_EDIT_ROLES_PATH = 'roles';
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { CollectionPageComponent } from './collection-page.component';
|
|
||||||
import { CollectionPageResolver } from './collection-page.resolver';
|
import { CollectionPageResolver } from './collection-page.resolver';
|
||||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
@@ -21,6 +20,7 @@ import {
|
|||||||
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||||
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -62,7 +62,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CollectionPageComponent,
|
component: ThemedCollectionPageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@@ -13,6 +13,7 @@ import { CollectionItemMapperComponent } from './collection-item-mapper/collecti
|
|||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CollectionFormModule } from './collection-form/collection-form.module';
|
import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||||
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -25,6 +26,7 @@ import { CollectionFormModule } from './collection-form/collection-form.module';
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
|
ThemedCollectionPageComponent,
|
||||||
CreateCollectionPageComponent,
|
CreateCollectionPageComponent,
|
||||||
DeleteCollectionPageComponent,
|
DeleteCollectionPageComponent,
|
||||||
EditItemTemplatePageComponent,
|
EditItemTemplatePageComponent,
|
||||||
|
@@ -15,6 +15,7 @@ import { Collection } from '../../../core/shared/collection.model';
|
|||||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
|
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||||
|
|
||||||
describe('CollectionMetadataComponent', () => {
|
describe('CollectionMetadataComponent', () => {
|
||||||
let comp: CollectionMetadataComponent;
|
let comp: CollectionMetadataComponent;
|
||||||
@@ -35,11 +36,13 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
self: { href: 'collection-selflink' }
|
self: { href: 'collection-selflink' }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const collectionTemplateHref = 'rest/api/test/collections/template';
|
||||||
|
|
||||||
const itemTemplateServiceStub = Object.assign({
|
const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
|
||||||
findByCollectionID: () => createSuccessfulRemoteDataObject$(template),
|
findByCollectionID: createSuccessfulRemoteDataObject$(template),
|
||||||
create: () => createSuccessfulRemoteDataObject$(template),
|
create: createSuccessfulRemoteDataObject$(template),
|
||||||
deleteByCollectionID: () => observableOf(true)
|
deleteByCollectionID: observableOf(true),
|
||||||
|
getCollectionEndpoint: observableOf(collectionTemplateHref),
|
||||||
});
|
});
|
||||||
|
|
||||||
const notificationsService = jasmine.createSpyObj('notificationsService', {
|
const notificationsService = jasmine.createSpyObj('notificationsService', {
|
||||||
@@ -50,7 +53,7 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
remove: {}
|
remove: {}
|
||||||
});
|
});
|
||||||
const requestService = jasmine.createSpyObj('requestService', {
|
const requestService = jasmine.createSpyObj('requestService', {
|
||||||
removeByHrefSubstring: {}
|
setStaleByHrefSubstring: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
@@ -87,14 +90,14 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
it('should navigate to the collection\'s itemtemplate page', () => {
|
it('should navigate to the collection\'s itemtemplate page', () => {
|
||||||
spyOn(router, 'navigate');
|
spyOn(router, 'navigate');
|
||||||
comp.addItemTemplate();
|
comp.addItemTemplate();
|
||||||
expect(router.navigate).toHaveBeenCalledWith(['collections', collection.uuid, 'itemtemplate']);
|
expect(router.navigate).toHaveBeenCalledWith([getCollectionItemTemplateRoute(collection.uuid)]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteItemTemplate', () => {
|
describe('deleteItemTemplate', () => {
|
||||||
describe('when delete returns a success', () => {
|
describe('when delete returns a success', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(true));
|
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true));
|
||||||
comp.deleteItemTemplate();
|
comp.deleteItemTemplate();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -103,14 +106,15 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should reset related object and request cache', () => {
|
it('should reset related object and request cache', () => {
|
||||||
expect(objectCache.remove).toHaveBeenCalledWith(template.self);
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collectionTemplateHref);
|
||||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(collection.self);
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(template.self);
|
||||||
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collection.self);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when delete returns a failure', () => {
|
describe('when delete returns a failure', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(false));
|
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
comp.deleteItemTemplate();
|
comp.deleteItemTemplate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -7,12 +7,13 @@ import { ItemTemplateDataService } from '../../../core/data/item-template-data.s
|
|||||||
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
||||||
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 { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||||
import { switchMap, take } from 'rxjs/operators';
|
import { switchMap, tap } from 'rxjs/operators';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
|
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for editing a collection's metadata
|
* Component for editing a collection's metadata
|
||||||
@@ -53,8 +54,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
|||||||
*/
|
*/
|
||||||
initTemplateItem() {
|
initTemplateItem() {
|
||||||
this.itemTemplateRD$ = this.dsoRD$.pipe(
|
this.itemTemplateRD$ = this.dsoRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
getRemoteDataPayload(),
|
|
||||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid))
|
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -64,19 +64,20 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
|||||||
*/
|
*/
|
||||||
addItemTemplate() {
|
addItemTemplate() {
|
||||||
const collection$ = this.dsoRD$.pipe(
|
const collection$ = this.dsoRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
getRemoteDataPayload(),
|
|
||||||
take(1)
|
|
||||||
);
|
);
|
||||||
const template$ = collection$.pipe(
|
const template$ = collection$.pipe(
|
||||||
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid)),
|
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
getRemoteDataPayload(),
|
)),
|
||||||
take(1)
|
);
|
||||||
|
const templateHref$ = collection$.pipe(
|
||||||
|
switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
combineLatestObservable(collection$, template$).subscribe(([collection, template]) => {
|
combineLatestObservable(collection$, template$, templateHref$).subscribe(([collection, template, templateHref]) => {
|
||||||
this.router.navigate(['collections', collection.uuid, 'itemtemplate']);
|
this.requestService.setStaleByHrefSubstring(templateHref);
|
||||||
|
this.router.navigate([getCollectionItemTemplateRoute(collection.uuid)]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,23 +86,30 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
|||||||
*/
|
*/
|
||||||
deleteItemTemplate() {
|
deleteItemTemplate() {
|
||||||
const collection$ = this.dsoRD$.pipe(
|
const collection$ = this.dsoRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
getRemoteDataPayload(),
|
|
||||||
take(1)
|
|
||||||
);
|
);
|
||||||
const template$ = collection$.pipe(
|
const template$ = collection$.pipe(
|
||||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
|
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid).pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
getRemoteDataPayload(),
|
)),
|
||||||
take(1)
|
);
|
||||||
|
const templateHref$ = collection$.pipe(
|
||||||
|
switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
|
||||||
);
|
);
|
||||||
|
|
||||||
combineLatestObservable(collection$, template$).pipe(
|
combineLatestObservable(collection$, template$, templateHref$).pipe(
|
||||||
switchMap(([collection, template]) => {
|
switchMap(([collection, template, templateHref]) => {
|
||||||
const success$ = this.itemTemplateService.deleteByCollectionID(template, collection.uuid);
|
return this.itemTemplateService.deleteByCollectionID(template, collection.uuid).pipe(
|
||||||
|
tap((success: boolean) => {
|
||||||
|
if (success) {
|
||||||
|
this.objectCache.remove(templateHref);
|
||||||
this.objectCache.remove(template.self);
|
this.objectCache.remove(template.self);
|
||||||
this.requestService.removeByHrefSubstring(collection.self);
|
this.requestService.setStaleByHrefSubstring(template.self);
|
||||||
return success$;
|
this.requestService.setStaleByHrefSubstring(templateHref);
|
||||||
|
this.requestService.setStaleByHrefSubstring(collection.self);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
).subscribe((success: boolean) => {
|
).subscribe((success: boolean) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
|
@@ -1,9 +1,13 @@
|
|||||||
<div class="container" *ngVar="(collectionRD$ | async)?.payload as collection">
|
<div class="container" *ngVar="(collectionRD$ | async)?.payload as collection">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12" *ngVar="(itemRD$ | async) as itemRD">
|
||||||
|
<ng-container *ngIf="itemRD?.hasSucceeded">
|
||||||
<h2 class="border-bottom">{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}</h2>
|
<h2 class="border-bottom">{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}</h2>
|
||||||
<ds-item-metadata [updateService]="itemTemplateService"></ds-item-metadata>
|
<ds-item-metadata [updateService]="itemTemplateService" [item]="itemRD?.payload"></ds-item-metadata>
|
||||||
<button [routerLink]="getCollectionEditUrl(collection)" class="btn btn-outline-secondary">{{ 'collection.edit.template.cancel' | translate }}</button>
|
<button [routerLink]="getCollectionEditUrl(collection)" class="btn btn-outline-secondary">{{ 'collection.edit.template.cancel' | translate }}</button>
|
||||||
|
</ng-container>
|
||||||
|
<ds-loading *ngIf="itemRD?.isLoading" [message]="'collection.edit.template.loading' | translate"></ds-loading>
|
||||||
|
<ds-alert *ngIf="itemRD?.hasFailed" [type]="AlertTypeEnum.Error" [content]="'collection.edit.template.error' | translate"></ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { getCollectionEditRoute } from '../collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../collection-page-routing-paths';
|
||||||
|
|
||||||
describe('EditItemTemplatePageComponent', () => {
|
describe('EditItemTemplatePageComponent', () => {
|
||||||
@@ -24,11 +24,14 @@ describe('EditItemTemplatePageComponent', () => {
|
|||||||
id: 'collection-id',
|
id: 'collection-id',
|
||||||
name: 'Fake Collection'
|
name: 'Fake Collection'
|
||||||
});
|
});
|
||||||
|
itemTemplateService = jasmine.createSpyObj('itemTemplateService', {
|
||||||
|
findByCollectionID: createSuccessfulRemoteDataObject$({})
|
||||||
|
});
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||||
declarations: [EditItemTemplatePageComponent],
|
declarations: [EditItemTemplatePageComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ItemTemplateDataService, useValue: {} },
|
{ provide: ItemTemplateDataService, useValue: itemTemplateService },
|
||||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } }
|
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -38,7 +41,6 @@ describe('EditItemTemplatePageComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(EditItemTemplatePageComponent);
|
fixture = TestBed.createComponent(EditItemTemplatePageComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
itemTemplateService = (comp as any).itemTemplateService;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -3,9 +3,12 @@ import { Observable } from 'rxjs';
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { first, map } from 'rxjs/operators';
|
import { first, map, switchMap } from 'rxjs/operators';
|
||||||
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
import { ItemTemplateDataService } from '../../core/data/item-template-data.service';
|
||||||
import { getCollectionEditRoute } from '../collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../collection-page-routing-paths';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
|
import { AlertType } from '../../shared/alert/aletr-type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-item-template-page',
|
selector: 'ds-edit-item-template-page',
|
||||||
@@ -21,12 +24,27 @@ export class EditItemTemplatePageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The template item
|
||||||
|
*/
|
||||||
|
itemRD$: Observable<RemoteData<Item>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The AlertType enumeration
|
||||||
|
* @type {AlertType}
|
||||||
|
*/
|
||||||
|
AlertTypeEnum = AlertType;
|
||||||
|
|
||||||
constructor(protected route: ActivatedRoute,
|
constructor(protected route: ActivatedRoute,
|
||||||
public itemTemplateService: ItemTemplateDataService) {
|
public itemTemplateService: ItemTemplateDataService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
|
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
|
||||||
|
this.itemRD$ = this.collectionRD$.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
switchMap((collection) => this.itemTemplateService.findByCollectionID(collection.id)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
26
src/app/+collection-page/themed-collection-page.component.ts
Normal file
26
src/app/+collection-page/themed-collection-page.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { CollectionPageComponent } from './collection-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for CollectionPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-community-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedCollectionPageComponent extends ThemedComponent<CollectionPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'CollectionPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+collection-page/collection-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./collection-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { CommunityPageComponent } from './community-page.component';
|
|
||||||
import { CommunityPageResolver } from './community-page.resolver';
|
import { CommunityPageResolver } from './community-page.resolver';
|
||||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
@@ -14,6 +13,7 @@ import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-rou
|
|||||||
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||||
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -45,7 +45,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CommunityPageComponent,
|
component: ThemedCommunityPageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@@ -11,6 +11,14 @@ import { CreateCommunityPageComponent } from './create-community-page/create-com
|
|||||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
import { CommunityFormModule } from './community-form/community-form.module';
|
import { CommunityFormModule } from './community-form/community-form.module';
|
||||||
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
|
|
||||||
|
const DECLARATIONS = [CommunityPageComponent,
|
||||||
|
ThemedCommunityPageComponent,
|
||||||
|
CommunityPageSubCollectionListComponent,
|
||||||
|
CommunityPageSubCommunityListComponent,
|
||||||
|
CreateCommunityPageComponent,
|
||||||
|
DeleteCommunityPageComponent];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -21,11 +29,10 @@ import { CommunityFormModule } from './community-form/community-form.module';
|
|||||||
CommunityFormModule
|
CommunityFormModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CommunityPageComponent,
|
...DECLARATIONS
|
||||||
CommunityPageSubCollectionListComponent,
|
],
|
||||||
CommunityPageSubCommunityListComponent,
|
exports: [
|
||||||
CreateCommunityPageComponent,
|
...DECLARATIONS
|
||||||
DeleteCommunityPageComponent
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
26
src/app/+community-page/themed-community-page.component.ts
Normal file
26
src/app/+community-page/themed-community-page.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { CommunityPageComponent } from './community-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for CommunityPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-community-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedCommunityPageComponent extends ThemedComponent<CommunityPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'CommunityPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+community-page/community-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./community-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { SubmissionImportExternalComponent } from '../submission/import-external/submission-import-external.component';
|
import { ThemedSubmissionImportExternalComponent } from '../submission/import-external/themed-submission-import-external.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -9,7 +9,7 @@ import { SubmissionImportExternalComponent } from '../submission/import-external
|
|||||||
{
|
{
|
||||||
canActivate: [ AuthenticatedGuard ],
|
canActivate: [ AuthenticatedGuard ],
|
||||||
path: '',
|
path: '',
|
||||||
component: SubmissionImportExternalComponent,
|
component: ThemedSubmissionImportExternalComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
data: {
|
data: {
|
||||||
title: 'submission.import-external.page.title'
|
title: 'submission.import-external.page.title'
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
FieldUpdate,
|
FieldUpdate,
|
||||||
FieldUpdates
|
FieldUpdates
|
||||||
@@ -30,7 +30,7 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
/**
|
/**
|
||||||
* The item to display the edit page for
|
* The item to display the edit page for
|
||||||
*/
|
*/
|
||||||
item: Item;
|
@Input() item: Item;
|
||||||
/**
|
/**
|
||||||
* The current values and updates for all this item's fields
|
* The current values and updates for all this item's fields
|
||||||
* Should be initialized in the initializeUpdates method of the child component
|
* Should be initialized in the initializeUpdates method of the child component
|
||||||
@@ -63,6 +63,10 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
* Initialize common properties between item-update components
|
* Initialize common properties between item-update components
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (hasValue(this.item)) {
|
||||||
|
this.setItem(this.item);
|
||||||
|
} else {
|
||||||
|
// The item wasn't provided through an input, retrieve it from the route instead.
|
||||||
this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe(
|
this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe(
|
||||||
map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)),
|
map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)),
|
||||||
map((data: any) => data.dso),
|
map((data: any) => data.dso),
|
||||||
@@ -74,11 +78,9 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
}),
|
}),
|
||||||
getAllSucceededRemoteData()
|
getAllSucceededRemoteData()
|
||||||
).subscribe((rd: RemoteData<Item>) => {
|
).subscribe((rd: RemoteData<Item>) => {
|
||||||
this.item = rd.payload;
|
this.setItem(rd.payload);
|
||||||
this.itemPageRoute = getItemPageRoute(this.item);
|
|
||||||
this.postItemInit();
|
|
||||||
this.initializeUpdates();
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.discardTimeOut = environment.item.edit.undoTimeout;
|
this.discardTimeOut = environment.item.edit.undoTimeout;
|
||||||
this.url = this.router.url;
|
this.url = this.router.url;
|
||||||
@@ -97,6 +99,13 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
|||||||
this.initializeUpdates();
|
this.initializeUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setItem(item: Item) {
|
||||||
|
this.item = item;
|
||||||
|
this.itemPageRoute = getItemPageRoute(this.item);
|
||||||
|
this.postItemInit();
|
||||||
|
this.initializeUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (hasValue(this.itemUpdateSubscription)) {
|
if (hasValue(this.itemUpdateSubscription)) {
|
||||||
this.itemUpdateSubscription.unsubscribe();
|
this.itemUpdateSubscription.unsubscribe();
|
||||||
|
@@ -5,11 +5,18 @@
|
|||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
<ul class="nav nav-tabs justify-content-start">
|
<ul class="nav nav-tabs justify-content-start">
|
||||||
<li *ngFor="let page of pages" class="nav-item">
|
<li *ngFor="let page of pages" class="nav-item">
|
||||||
<a class="nav-link"
|
<a *ngIf="(page.enabled | async)"
|
||||||
[ngClass]="{'active' : page === currentPage}"
|
class="nav-link"
|
||||||
[routerLink]="['./' + page]">
|
[ngClass]="{'active' : page.page === currentPage}"
|
||||||
{{'item.edit.tabs.' + page + '.head' | translate}}
|
[routerLink]="['./' + page.page]">
|
||||||
|
{{'item.edit.tabs.' + page.page + '.head' | translate}}
|
||||||
</a>
|
</a>
|
||||||
|
<span [ngbTooltip]="'item.edit.tabs.disabled.tooltip' | translate">
|
||||||
|
<button *ngIf="!(page.enabled | async)"
|
||||||
|
class="nav-link disabled">
|
||||||
|
{{'item.edit.tabs.' + page.page + '.head' | translate}}
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tab-pane active">
|
<div class="tab-pane active">
|
||||||
|
@@ -0,0 +1,107 @@
|
|||||||
|
import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute, ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
|
||||||
|
import { EditItemPageComponent } from './edit-item-page.component';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
|
describe('ItemPageComponent', () => {
|
||||||
|
let comp: EditItemPageComponent;
|
||||||
|
let fixture: ComponentFixture<EditItemPageComponent>;
|
||||||
|
|
||||||
|
class AcceptAllGuard implements CanActivate {
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||||
|
return observableOf(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line:max-classes-per-file
|
||||||
|
class AcceptNoneGuard implements CanActivate {
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
|
||||||
|
console.log('BLA');
|
||||||
|
return observableOf(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const accesiblePages = ['accessible'];
|
||||||
|
const inaccesiblePages = ['inaccessible', 'inaccessibleDoubleGuard'];
|
||||||
|
const mockRoute = {
|
||||||
|
snapshot: {
|
||||||
|
firstChild: {
|
||||||
|
routeConfig: {
|
||||||
|
path: accesiblePages[0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
routerState: {
|
||||||
|
snapshot: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
routeConfig: {
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: accesiblePages[0],
|
||||||
|
canActivate: [AcceptAllGuard]
|
||||||
|
}, {
|
||||||
|
path: inaccesiblePages[0],
|
||||||
|
canActivate: [AcceptNoneGuard]
|
||||||
|
}, {
|
||||||
|
path: inaccesiblePages[1],
|
||||||
|
canActivate: [AcceptAllGuard, AcceptNoneGuard]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
data: observableOf({dso: createSuccessfulRemoteDataObject(new Item())})
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockRouter = {
|
||||||
|
routerState: {
|
||||||
|
snapshot: undefined
|
||||||
|
},
|
||||||
|
events: observableOf(undefined)
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: TranslateLoaderMock
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [EditItemPageComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: mockRoute },
|
||||||
|
{ provide: Router, useValue: mockRouter },
|
||||||
|
AcceptAllGuard,
|
||||||
|
AcceptNoneGuard,
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(EditItemPageComponent, {
|
||||||
|
set: { changeDetection: ChangeDetectionStrategy.Default }
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
fixture = TestBed.createComponent(EditItemPageComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
spyOn((comp as any).injector, 'get').and.callFake((a) => new a());
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('ngOnInit', () => {
|
||||||
|
it('should enable tabs that the user can activate', fakeAsync(() => {
|
||||||
|
const enabledItems = fixture.debugElement.queryAll(By.css('a.nav-link'));
|
||||||
|
expect(enabledItems.length).toBe(accesiblePages.length);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should disable tabs that the user can not activate', () => {
|
||||||
|
const disabledItems = fixture.debugElement.queryAll(By.css('button.nav-link.disabled'));
|
||||||
|
expect(disabledItems.length).toBe(inaccesiblePages.length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,12 +1,13 @@
|
|||||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, CanActivate, Route, Router } from '@angular/router';
|
||||||
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 { Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||||
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-item-page',
|
selector: 'ds-edit-item-page',
|
||||||
@@ -35,9 +36,9 @@ export class EditItemPageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* All possible page outlet strings
|
* All possible page outlet strings
|
||||||
*/
|
*/
|
||||||
pages: string[];
|
pages: { page: string, enabled: Observable<boolean> }[];
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router) {
|
constructor(private route: ActivatedRoute, private router: Router, private injector: Injector) {
|
||||||
this.router.events.subscribe(() => {
|
this.router.events.subscribe(() => {
|
||||||
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
|
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
|
||||||
});
|
});
|
||||||
@@ -45,8 +46,20 @@ export class EditItemPageComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.pages = this.route.routeConfig.children
|
this.pages = this.route.routeConfig.children
|
||||||
.map((child: any) => child.path)
|
.filter((child: Route) => isNotEmpty(child.path))
|
||||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
.map((child: Route) => {
|
||||||
|
let enabled = observableOf(true);
|
||||||
|
if (isNotEmpty(child.canActivate)) {
|
||||||
|
enabled = observableCombineLatest(child.canActivate.map((guardConstructor: GenericConstructor<CanActivate>) => {
|
||||||
|
const guard: CanActivate = this.injector.get<CanActivate>(guardConstructor);
|
||||||
|
return guard.canActivate(this.route.snapshot, this.router.routerState.snapshot);
|
||||||
|
})
|
||||||
|
).pipe(
|
||||||
|
map((canActivateOutcomes: any[]) => canActivateOutcomes.every((e) => e === true))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return { page: child.path, enabled: enabled };
|
||||||
|
}); // ignore reroutes
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
|
this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -22,15 +22,17 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
|
|||||||
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
import {
|
import {
|
||||||
ITEM_EDIT_AUTHORIZATIONS_PATH,
|
ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||||
ITEM_EDIT_MOVE_PATH,
|
|
||||||
ITEM_EDIT_DELETE_PATH,
|
ITEM_EDIT_DELETE_PATH,
|
||||||
ITEM_EDIT_PUBLIC_PATH,
|
ITEM_EDIT_MOVE_PATH,
|
||||||
ITEM_EDIT_PRIVATE_PATH,
|
ITEM_EDIT_PRIVATE_PATH,
|
||||||
|
ITEM_EDIT_PUBLIC_PATH,
|
||||||
ITEM_EDIT_REINSTATE_PATH,
|
ITEM_EDIT_REINSTATE_PATH,
|
||||||
ITEM_EDIT_WITHDRAW_PATH
|
ITEM_EDIT_WITHDRAW_PATH
|
||||||
} from './edit-item-page.routing-paths';
|
} from './edit-item-page.routing-paths';
|
||||||
import { ItemPageReinstateGuard } from './item-page-reinstate.guard';
|
import { ItemPageReinstateGuard } from './item-page-reinstate.guard';
|
||||||
import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
||||||
|
import { ItemPageEditMetadataGuard } from '../item-page-edit-metadata.guard';
|
||||||
|
import { ItemPageAdministratorGuard } from '../item-page-administrator.guard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Routing module that handles the routing for the Edit Item page administrator functionality
|
* Routing module that handles the routing for the Edit Item page administrator functionality
|
||||||
@@ -57,22 +59,26 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
|||||||
{
|
{
|
||||||
path: 'status',
|
path: 'status',
|
||||||
component: ItemStatusComponent,
|
component: ItemStatusComponent,
|
||||||
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'bitstreams',
|
path: 'bitstreams',
|
||||||
component: ItemBitstreamsComponent,
|
component: ItemBitstreamsComponent,
|
||||||
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'metadata',
|
path: 'metadata',
|
||||||
component: ItemMetadataComponent,
|
component: ItemMetadataComponent,
|
||||||
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageEditMetadataGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'relationships',
|
path: 'relationships',
|
||||||
component: ItemRelationshipsComponent,
|
component: ItemRelationshipsComponent,
|
||||||
data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageEditMetadataGuard]
|
||||||
},
|
},
|
||||||
/* TODO - uncomment & fix when view page exists
|
/* TODO - uncomment & fix when view page exists
|
||||||
{
|
{
|
||||||
@@ -89,12 +95,14 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
|||||||
{
|
{
|
||||||
path: 'versionhistory',
|
path: 'versionhistory',
|
||||||
component: ItemVersionHistoryComponent,
|
component: ItemVersionHistoryComponent,
|
||||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageAdministratorGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'mapper',
|
path: 'mapper',
|
||||||
component: ItemCollectionMapperComponent,
|
component: ItemCollectionMapperComponent,
|
||||||
data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true }
|
data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true },
|
||||||
|
canActivate: [ItemPageAdministratorGuard]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -165,7 +173,9 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
|||||||
ResourcePolicyResolver,
|
ResourcePolicyResolver,
|
||||||
ResourcePolicyTargetResolver,
|
ResourcePolicyTargetResolver,
|
||||||
ItemPageReinstateGuard,
|
ItemPageReinstateGuard,
|
||||||
ItemPageWithdrawGuard
|
ItemPageWithdrawGuard,
|
||||||
|
ItemPageAdministratorGuard,
|
||||||
|
ItemPageEditMetadataGuard,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class EditItemPageRoutingModule {
|
export class EditItemPageRoutingModule {
|
||||||
|
@@ -18,9 +18,8 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a full item page.
|
||||||
* The route parameter 'id' is used to request the item it represents.
|
* The route parameter 'id' is used to request the item it represents.
|
||||||
* All fields of the item that should be displayed, are defined in its template.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
25
src/app/+item-page/full/themed-full-item-page.component.ts
Normal file
25
src/app/+item-page/full/themed-full-item-page.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { FullItemPageComponent } from './full-item-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for FullItemPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-full-item-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedFullItemPageComponent extends ThemedComponent<FullItemPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'FullItemPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+item-page/full/full-item-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./full-item-page.component`);
|
||||||
|
}
|
||||||
|
}
|
31
src/app/+item-page/item-page-edit-metadata.guard.ts
Normal file
31
src/app/+item-page/item-page-edit-metadata.guard.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { ItemPageResolver } from './item-page.resolver';
|
||||||
|
import { Item } from '../core/shared/item.model';
|
||||||
|
import { DsoPageFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-feature.guard';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights
|
||||||
|
*/
|
||||||
|
export class ItemPageEditMetadataGuard extends DsoPageFeatureGuard<Item> {
|
||||||
|
constructor(protected resolver: ItemPageResolver,
|
||||||
|
protected authorizationService: AuthorizationDataService,
|
||||||
|
protected router: Router,
|
||||||
|
protected authService: AuthService) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check edit metadata authorization rights
|
||||||
|
*/
|
||||||
|
getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
|
return observableOf(FeatureID.CanEditMetadata);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,18 +1,17 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { ItemPageComponent } from './simple/item-page.component';
|
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
|
||||||
import { ItemPageResolver } from './item-page.resolver';
|
import { ItemPageResolver } from './item-page.resolver';
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
|
import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||||
import { LinkService } from '../core/cache/builders/link.service';
|
import { LinkService } from '../core/cache/builders/link.service';
|
||||||
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
||||||
import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths';
|
import { ITEM_EDIT_PATH, UPLOAD_BITSTREAM_PATH } from './item-page-routing-paths';
|
||||||
import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
||||||
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||||
|
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -27,18 +26,17 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ItemPageComponent,
|
component: ThemedItemPageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'full',
|
path: 'full',
|
||||||
component: FullItemPageComponent,
|
component: ThemedFullItemPageComponent,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ITEM_EDIT_PATH,
|
path: ITEM_EDIT_PATH,
|
||||||
loadChildren: () => import('./edit-item-page/edit-item-page.module')
|
loadChildren: () => import('./edit-item-page/edit-item-page.module')
|
||||||
.then((m) => m.EditItemPageModule),
|
.then((m) => m.EditItemPageModule),
|
||||||
canActivate: [ItemPageAdministratorGuard]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: UPLOAD_BITSTREAM_PATH,
|
path: UPLOAD_BITSTREAM_PATH,
|
||||||
@@ -68,7 +66,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
ItemBreadcrumbResolver,
|
ItemBreadcrumbResolver,
|
||||||
DSOBreadcrumbsService,
|
DSOBreadcrumbsService,
|
||||||
LinkService,
|
LinkService,
|
||||||
ItemPageAdministratorGuard
|
ItemPageAdministratorGuard,
|
||||||
]
|
]
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@@ -25,6 +25,8 @@ import { AbstractIncrementalListComponent } from './simple/abstract-incremental-
|
|||||||
import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component';
|
import { UntypedItemComponent } from './simple/item-types/untyped-item/untyped-item.component';
|
||||||
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||||
|
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
|
||||||
|
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -34,7 +36,9 @@ const ENTRY_COMPONENTS = [
|
|||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
ItemPageComponent,
|
ItemPageComponent,
|
||||||
|
ThemedItemPageComponent,
|
||||||
FullItemPageComponent,
|
FullItemPageComponent,
|
||||||
|
ThemedFullItemPageComponent,
|
||||||
MetadataUriValuesComponent,
|
MetadataUriValuesComponent,
|
||||||
ItemPageAuthorFieldComponent,
|
ItemPageAuthorFieldComponent,
|
||||||
ItemPageDateFieldComponent,
|
ItemPageDateFieldComponent,
|
||||||
|
27
src/app/+item-page/simple/themed-item-page.component.ts
Normal file
27
src/app/+item-page/simple/themed-item-page.component.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { ItemPageComponent } from './item-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for ItemPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-item-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ThemedItemPageComponent extends ThemedComponent<ItemPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'ItemPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+item-page/simple/item-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./item-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,14 +1,13 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { LoginPageComponent } from './login-page.component';
|
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
|
import { ThemedLoginPageComponent } from './themed-login-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{ path: '', pathMatch: 'full', component: LoginPageComponent, resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'login', title: 'login.title' } }
|
{ path: '', pathMatch: 'full', component: ThemedLoginPageComponent, resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { breadcrumbKey: 'login', title: 'login.title' } }
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { LoginPageComponent } from './login-page.component';
|
import { LoginPageComponent } from './login-page.component';
|
||||||
import { LoginPageRoutingModule } from './login-page-routing.module';
|
import { LoginPageRoutingModule } from './login-page-routing.module';
|
||||||
|
import { ThemedLoginPageComponent } from './themed-login-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -11,7 +12,8 @@ import { LoginPageRoutingModule } from './login-page-routing.module';
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
LoginPageComponent
|
LoginPageComponent,
|
||||||
|
ThemedLoginPageComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class LoginPageModule {
|
export class LoginPageModule {
|
||||||
|
25
src/app/+login-page/themed-login-page.component.ts
Normal file
25
src/app/+login-page/themed-login-page.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { LoginPageComponent } from './login-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for LoginPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-login-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedLoginPageComponent extends ThemedComponent<LoginPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'LoginPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+login-page/login-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./login-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { LogoutPageComponent } from './logout-page.component';
|
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
|
import { ThemedLogoutPageComponent } from './themed-logout-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -10,7 +9,7 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
|||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: '',
|
path: '',
|
||||||
component: LogoutPageComponent,
|
component: ThemedLogoutPageComponent,
|
||||||
data: { title: 'logout.title' }
|
data: { title: 'logout.title' }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
import { LogoutPageComponent } from './logout-page.component';
|
import { LogoutPageComponent } from './logout-page.component';
|
||||||
import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
||||||
|
import { ThemedLogoutPageComponent } from './themed-logout-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -11,7 +12,8 @@ import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
LogoutPageComponent
|
LogoutPageComponent,
|
||||||
|
ThemedLogoutPageComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class LogoutPageModule {
|
export class LogoutPageModule {
|
||||||
|
25
src/app/+logout-page/themed-logout-page.component.ts
Normal file
25
src/app/+logout-page/themed-logout-page.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { LogoutPageComponent } from './logout-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for LogoutPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-logout-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedLogoutPageComponent extends ThemedComponent<LogoutPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'LogoutPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+logout-page/logout-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./logout-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
import { LookupGuard } from './lookup-guard';
|
import { LookupGuard } from './lookup-guard';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, UrlSegment } from '@angular/router';
|
import { RouterModule, UrlSegment } from '@angular/router';
|
||||||
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
|
||||||
import { isNotEmpty } from '../shared/empty.util';
|
import { isNotEmpty } from '../shared/empty.util';
|
||||||
|
import { ThemedObjectNotFoundComponent } from './objectnotfound/themed-objectnotfound.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -10,7 +10,7 @@ import { isNotEmpty } from '../shared/empty.util';
|
|||||||
{
|
{
|
||||||
matcher: urlMatcher,
|
matcher: urlMatcher,
|
||||||
canActivate: [LookupGuard],
|
canActivate: [LookupGuard],
|
||||||
component: ObjectNotFoundComponent }
|
component: ThemedObjectNotFoundComponent }
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -4,6 +4,7 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { LookupRoutingModule } from './lookup-by-id-routing.module';
|
import { LookupRoutingModule } from './lookup-by-id-routing.module';
|
||||||
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
||||||
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||||
|
import { ThemedObjectNotFoundComponent } from './objectnotfound/themed-objectnotfound.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -12,7 +13,8 @@ import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ObjectNotFoundComponent
|
ObjectNotFoundComponent,
|
||||||
|
ThemedObjectNotFoundComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DsoRedirectDataService
|
DsoRedirectDataService
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { ObjectNotFoundComponent } from './objectnotfound.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for ObjectNotFoundComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-objnotfound',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedObjectNotFoundComponent extends ThemedComponent<ObjectNotFoundComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'ObjectNotFoundComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+lookup-by-id/objectnotfound/objectnotfound.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./objectnotfound.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,15 +1,14 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { MyDSpacePageComponent } from './my-dspace-page.component';
|
|
||||||
import { MyDSpaceGuard } from './my-dspace.guard';
|
import { MyDSpaceGuard } from './my-dspace.guard';
|
||||||
|
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: MyDSpacePageComponent,
|
component: ThemedMyDSpacePageComponent,
|
||||||
data: { title: 'mydspace.title' },
|
data: { title: 'mydspace.title' },
|
||||||
canActivate: [
|
canActivate: [
|
||||||
MyDSpaceGuard
|
MyDSpaceGuard
|
||||||
|
@@ -11,6 +11,15 @@ import { MyDSpaceGuard } from './my-dspace.guard';
|
|||||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
||||||
import { MyDspaceSearchModule } from './my-dspace-search.module';
|
import { MyDspaceSearchModule } from './my-dspace-search.module';
|
||||||
|
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
MyDSpacePageComponent,
|
||||||
|
ThemedMyDSpacePageComponent,
|
||||||
|
MyDSpaceResultsComponent,
|
||||||
|
MyDSpaceNewSubmissionComponent,
|
||||||
|
CollectionSelectorComponent
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -19,16 +28,12 @@ import { MyDspaceSearchModule } from './my-dspace-search.module';
|
|||||||
MyDspacePageRoutingModule,
|
MyDspacePageRoutingModule,
|
||||||
MyDspaceSearchModule.withEntryComponents()
|
MyDspaceSearchModule.withEntryComponents()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: DECLARATIONS,
|
||||||
MyDSpacePageComponent,
|
|
||||||
MyDSpaceResultsComponent,
|
|
||||||
MyDSpaceNewSubmissionComponent,
|
|
||||||
CollectionSelectorComponent
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
MyDSpaceGuard,
|
MyDSpaceGuard,
|
||||||
MyDSpaceConfigurationService
|
MyDSpaceConfigurationService
|
||||||
]
|
],
|
||||||
|
exports: DECLARATIONS,
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
27
src/app/+my-dspace-page/themed-my-dspace-page.component.ts
Normal file
27
src/app/+my-dspace-page/themed-my-dspace-page.component.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { MyDSpacePageComponent } from './my-dspace-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for MyDSpacePageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-my-dspace-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedMyDSpacePageComponent extends ThemedComponent<MyDSpacePageComponent> {
|
||||||
|
protected inAndOutputNames: (keyof MyDSpacePageComponent & keyof this)[];
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'MyDSpacePageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+my-dspace-page/my-dspace-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./my-dspace-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,11 +2,11 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
|
||||||
import { SearchPageComponent } from './search-page.component';
|
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
import { SearchPageModule } from './search-page.module';
|
import { SearchPageModule } from './search-page.module';
|
||||||
|
import { ThemedSearchPageComponent } from './themed-search-page.component';
|
||||||
|
import { ThemedConfigurationSearchPageComponent } from './themed-configuration-search-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -15,8 +15,8 @@ import { SearchPageModule } from './search-page.module';
|
|||||||
path: '',
|
path: '',
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { title: 'search.title', breadcrumbKey: 'search' },
|
resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { title: 'search.title', breadcrumbKey: 'search' },
|
||||||
children: [
|
children: [
|
||||||
{ path: '', component: SearchPageComponent },
|
{ path: '', component: ThemedSearchPageComponent },
|
||||||
{ path: ':configuration', component: ConfigurationSearchPageComponent, canActivate: [ConfigurationSearchPageGuard] }
|
{ path: ':configuration', component: ThemedConfigurationSearchPageComponent, canActivate: [ConfigurationSearchPageGuard] }
|
||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
)
|
)
|
||||||
|
@@ -13,11 +13,13 @@ import { SearchFilterService } from '../core/shared/search/search-filter.service
|
|||||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
import { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
||||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||||
|
import { ThemedSearchPageComponent } from './themed-search-page.component';
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
SearchPageComponent,
|
SearchPageComponent,
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
SearchTrackerComponent
|
SearchTrackerComponent,
|
||||||
|
ThemedSearchPageComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -0,0 +1,72 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Context } from '../core/shared/context.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for ConfigurationSearchPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-configuration-search-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedConfigurationSearchPageComponent extends ThemedComponent<ConfigurationSearchPageComponent> {
|
||||||
|
/**
|
||||||
|
* The configuration to use for the search options
|
||||||
|
* If empty, the configuration will be determined by the route parameter called 'configuration'
|
||||||
|
*/
|
||||||
|
@Input() configuration: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual query for the fixed filter.
|
||||||
|
* If empty, the query will be determined by the route parameter called 'filter'
|
||||||
|
*/
|
||||||
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the search component should show results on the current page
|
||||||
|
*/
|
||||||
|
@Input() inPlaceSearch = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the search bar should be visible
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
searchEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of the sidebar (bootstrap columns)
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
sideBarWidth = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently applied configuration (determines title of search)
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
configuration$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current context
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
context: Context;
|
||||||
|
|
||||||
|
protected inAndOutputNames: (keyof ConfigurationSearchPageComponent & keyof this)[] =
|
||||||
|
['configuration', 'fixedFilterQuery', 'inPlaceSearch', 'searchEnabled', 'sideBarWidth', 'configuration$', 'context'];
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'ConfigurationSearchPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+search-page/configuration-search-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./configuration-search-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/app/+search-page/themed-search-page.component.ts
Normal file
26
src/app/+search-page/themed-search-page.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { SearchPageComponent } from './search-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for SearchPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-search-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedSearchPageComponent extends ThemedComponent<SearchPageComponent> {
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'SearchPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/+search-page/search-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./search-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component';
|
import { ThemedSubmissionSubmitComponent } from '../submission/submit/themed-submission-submit.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -11,7 +11,7 @@ import { SubmissionSubmitComponent } from '../submission/submit/submission-submi
|
|||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: '',
|
path: '',
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
component: SubmissionSubmitComponent,
|
component: ThemedSubmissionSubmitComponent,
|
||||||
data: { title: 'submission.submit.title' }
|
data: { title: 'submission.submit.title' }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { WorkflowItemDeleteComponent } from './workflow-item-delete.component';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for WorkflowItemDeleteComponent
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-workflow-item-delete',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedWorkflowItemDeleteComponent extends ThemedComponent<WorkflowItemDeleteComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'WorkflowItemDeleteComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./workflow-item-delete.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { WorkflowItemSendBackComponent } from './workflow-item-send-back.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for WorkflowItemActionPageComponent
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-workflow-item-send-back',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedWorkflowItemSendBackComponent extends ThemedComponent<WorkflowItemSendBackComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'WorkflowItemSendBackComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/+workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./workflow-item-send-back.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,15 +2,11 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { SubmissionEditComponent } from '../submission/edit/submission-edit.component';
|
|
||||||
import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component';
|
|
||||||
import { WorkflowItemPageResolver } from './workflow-item-page.resolver';
|
import { WorkflowItemPageResolver } from './workflow-item-page.resolver';
|
||||||
import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component';
|
import { WORKFLOW_ITEM_DELETE_PATH, WORKFLOW_ITEM_EDIT_PATH, WORKFLOW_ITEM_SEND_BACK_PATH } from './workflowitems-edit-page-routing-paths';
|
||||||
import {
|
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
|
||||||
WORKFLOW_ITEM_SEND_BACK_PATH,
|
import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component';
|
||||||
WORKFLOW_ITEM_DELETE_PATH,
|
import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component';
|
||||||
WORKFLOW_ITEM_EDIT_PATH
|
|
||||||
} from './workflowitems-edit-page-routing-paths';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,19 +18,19 @@ import {
|
|||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: WORKFLOW_ITEM_EDIT_PATH,
|
path: WORKFLOW_ITEM_EDIT_PATH,
|
||||||
component: SubmissionEditComponent,
|
component: ThemedSubmissionEditComponent,
|
||||||
data: { title: 'submission.edit.title' }
|
data: { title: 'submission.edit.title' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: WORKFLOW_ITEM_DELETE_PATH,
|
path: WORKFLOW_ITEM_DELETE_PATH,
|
||||||
component: WorkflowItemDeleteComponent,
|
component: ThemedWorkflowItemDeleteComponent,
|
||||||
data: { title: 'workflow-item.delete.title' }
|
data: { title: 'workflow-item.delete.title' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: WORKFLOW_ITEM_SEND_BACK_PATH,
|
path: WORKFLOW_ITEM_SEND_BACK_PATH,
|
||||||
component: WorkflowItemSendBackComponent,
|
component: ThemedWorkflowItemSendBackComponent,
|
||||||
data: { title: 'workflow-item.send-back.title' }
|
data: { title: 'workflow-item.send-back.title' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@@ -5,6 +5,8 @@ import { WorkflowItemsEditPageRoutingModule } from './workflowitems-edit-page-ro
|
|||||||
import { SubmissionModule } from '../submission/submission.module';
|
import { SubmissionModule } from '../submission/submission.module';
|
||||||
import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component';
|
import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.component';
|
||||||
import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component';
|
import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component';
|
||||||
|
import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component';
|
||||||
|
import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -13,7 +15,7 @@ import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflo
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
SubmissionModule,
|
SubmissionModule,
|
||||||
],
|
],
|
||||||
declarations: [WorkflowItemDeleteComponent, WorkflowItemSendBackComponent]
|
declarations: [WorkflowItemDeleteComponent, ThemedWorkflowItemDeleteComponent, WorkflowItemSendBackComponent, ThemedWorkflowItemSendBackComponent]
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* This module handles all modules that need to access the workflowitems edit page.
|
* This module handles all modules that need to access the workflowitems edit page.
|
||||||
|
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { SubmissionEditComponent } from '../submission/edit/submission-edit.component';
|
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -11,7 +11,7 @@ import { SubmissionEditComponent } from '../submission/edit/submission-edit.comp
|
|||||||
{
|
{
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
path: ':id/edit',
|
path: ':id/edit',
|
||||||
component: SubmissionEditComponent,
|
component: ThemedSubmissionEditComponent,
|
||||||
data: { title: 'submission.edit.title' }
|
data: { title: 'submission.edit.title' }
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
@@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||||
|
|
||||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
|
||||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||||
import {
|
import {
|
||||||
@@ -23,7 +22,8 @@ import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
|||||||
import { ReloadGuard } from './core/reload/reload.guard';
|
import { ReloadGuard } from './core/reload/reload.guard';
|
||||||
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
|
||||||
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||||
import { ForbiddenComponent } from './forbidden/forbidden.component';
|
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||||
|
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||||
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -32,7 +32,7 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
path: '', canActivate: [AuthBlockingGuard],
|
path: '', canActivate: [AuthBlockingGuard],
|
||||||
children: [
|
children: [
|
||||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||||
{ path: 'reload/:rnd', component: PageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] },
|
{ path: 'reload/:rnd', component: ThemedPageNotFoundComponent, pathMatch: 'full', canActivate: [ReloadGuard] },
|
||||||
{
|
{
|
||||||
path: 'home',
|
path: 'home',
|
||||||
loadChildren: () => import('./+home-page/home-page.module')
|
loadChildren: () => import('./+home-page/home-page.module')
|
||||||
@@ -175,7 +175,7 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: FORBIDDEN_PATH,
|
path: FORBIDDEN_PATH,
|
||||||
component: ForbiddenComponent
|
component: ThemedForbiddenComponent
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'statistics',
|
path: 'statistics',
|
||||||
@@ -187,7 +187,7 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
loadChildren: () => import('./access-control/access-control.module').then((m) => m.AccessControlModule),
|
loadChildren: () => import('./access-control/access-control.module').then((m) => m.AccessControlModule),
|
||||||
canActivate: [GroupAdministratorGuard],
|
canActivate: [GroupAdministratorGuard],
|
||||||
},
|
},
|
||||||
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||||
]}
|
]}
|
||||||
],{
|
],{
|
||||||
onSameUrlNavigation: 'reload',
|
onSameUrlNavigation: 'reload',
|
||||||
|
@@ -35,6 +35,7 @@ import { provideMockStore } from '@ngrx/store/testing';
|
|||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||||
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
|
||||||
let comp: AppComponent;
|
let comp: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
@@ -45,13 +46,18 @@ const initialState = {
|
|||||||
|
|
||||||
describe('App component', () => {
|
describe('App component', () => {
|
||||||
|
|
||||||
|
let breadcrumbsServiceSpy;
|
||||||
|
|
||||||
function getMockLocaleService(): LocaleService {
|
function getMockLocaleService(): LocaleService {
|
||||||
return jasmine.createSpyObj('LocaleService', {
|
return jasmine.createSpyObj('LocaleService', {
|
||||||
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTestBedConf = {
|
const getDefaultTestBedConf = () => {
|
||||||
|
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
||||||
|
|
||||||
|
return {
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||||
@@ -76,16 +82,18 @@ describe('App component', () => {
|
|||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
|
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
||||||
provideMockStore({ initialState }),
|
provideMockStore({ initialState }),
|
||||||
AppComponent,
|
AppComponent,
|
||||||
RouteService
|
RouteService
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// waitForAsync beforeEach
|
// waitForAsync beforeEach
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
return TestBed.configureTestingModule(defaultTestBedConf);
|
return TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// synchronous beforeEach
|
// synchronous beforeEach
|
||||||
@@ -120,13 +128,19 @@ describe('App component', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('the constructor', () => {
|
||||||
|
it('should call breadcrumbsService.listenForRouteChanges', () => {
|
||||||
|
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('when GoogleAnalyticsService is provided', () => {
|
describe('when GoogleAnalyticsService is provided', () => {
|
||||||
let googleAnalyticsSpy;
|
let googleAnalyticsSpy;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
TestBed.configureTestingModule(defaultTestBedConf);
|
TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
||||||
'addTrackingIdToPage',
|
'addTrackingIdToPage',
|
||||||
]);
|
]);
|
||||||
@@ -154,7 +168,7 @@ describe('App component', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
TestBed.configureTestingModule(defaultTestBedConf);
|
TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||||
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
||||||
document = TestBed.inject(DOCUMENT);
|
document = TestBed.inject(DOCUMENT);
|
||||||
headSpy = jasmine.createSpyObj('head', ['appendChild']);
|
headSpy = jasmine.createSpyObj('head', ['appendChild']);
|
||||||
|
@@ -36,6 +36,7 @@ import { DOCUMENT } from '@angular/common';
|
|||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||||
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-app',
|
selector: 'ds-app',
|
||||||
@@ -73,6 +74,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
private menuService: MenuService,
|
private menuService: MenuService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
private localeService: LocaleService,
|
private localeService: LocaleService,
|
||||||
|
private breadcrumbsService: BreadcrumbsService,
|
||||||
@Optional() private cookiesService: KlaroService,
|
@Optional() private cookiesService: KlaroService,
|
||||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||||
) {
|
) {
|
||||||
@@ -106,6 +108,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
angulartics2DSpace.startTracking();
|
angulartics2DSpace.startTracking();
|
||||||
|
|
||||||
metadata.listenForRouteChange();
|
metadata.listenForRouteChange();
|
||||||
|
breadcrumbsService.listenForRouteChanges();
|
||||||
|
|
||||||
if (environment.debug) {
|
if (environment.debug) {
|
||||||
console.info(environment);
|
console.info(environment);
|
||||||
|
@@ -46,6 +46,11 @@ import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
|||||||
import { RootComponent } from './root/root.component';
|
import { RootComponent } from './root/root.component';
|
||||||
import { ThemedRootComponent } from './root/themed-root.component';
|
import { ThemedRootComponent } from './root/themed-root.component';
|
||||||
import { ThemedEntryComponentModule } from '../themes/themed-entry-component.module';
|
import { ThemedEntryComponentModule } from '../themes/themed-entry-component.module';
|
||||||
|
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||||
|
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||||
|
import { ThemedHeaderComponent } from './header/themed-header.component';
|
||||||
|
import { ThemedFooterComponent } from './footer/themed-footer.component';
|
||||||
|
import { ThemedBreadcrumbsComponent } from './breadcrumbs/themed-breadcrumbs.component';
|
||||||
|
|
||||||
export function getBase() {
|
export function getBase() {
|
||||||
return environment.ui.nameSpace;
|
return environment.ui.nameSpace;
|
||||||
@@ -127,17 +132,22 @@ const DECLARATIONS = [
|
|||||||
RootComponent,
|
RootComponent,
|
||||||
ThemedRootComponent,
|
ThemedRootComponent,
|
||||||
HeaderComponent,
|
HeaderComponent,
|
||||||
|
ThemedHeaderComponent,
|
||||||
HeaderNavbarWrapperComponent,
|
HeaderNavbarWrapperComponent,
|
||||||
AdminSidebarComponent,
|
AdminSidebarComponent,
|
||||||
AdminSidebarSectionComponent,
|
AdminSidebarSectionComponent,
|
||||||
ExpandableAdminSidebarSectionComponent,
|
ExpandableAdminSidebarSectionComponent,
|
||||||
FooterComponent,
|
FooterComponent,
|
||||||
|
ThemedFooterComponent,
|
||||||
PageNotFoundComponent,
|
PageNotFoundComponent,
|
||||||
|
ThemedPageNotFoundComponent,
|
||||||
NotificationComponent,
|
NotificationComponent,
|
||||||
NotificationsBoardComponent,
|
NotificationsBoardComponent,
|
||||||
SearchNavbarComponent,
|
SearchNavbarComponent,
|
||||||
BreadcrumbsComponent,
|
BreadcrumbsComponent,
|
||||||
|
ThemedBreadcrumbsComponent,
|
||||||
ForbiddenComponent,
|
ForbiddenComponent,
|
||||||
|
ThemedForbiddenComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const EXPORTS = [
|
const EXPORTS = [
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { BreadcrumbsService } from '../../core/breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsProviderService } from '../../core/breadcrumbs/breadcrumbsProviderService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for breadcrumb configuration objects
|
* Interface for breadcrumb configuration objects
|
||||||
@@ -7,7 +7,7 @@ export interface BreadcrumbConfig<T> {
|
|||||||
/**
|
/**
|
||||||
* The service used to calculate the breadcrumb object
|
* The service used to calculate the breadcrumb object
|
||||||
*/
|
*/
|
||||||
provider: BreadcrumbsService<T>;
|
provider: BreadcrumbsProviderService<T>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key that is used to calculate the breadcrumb display value
|
* The key that is used to calculate the breadcrumb display value
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
|
||||||
<nav *ngIf="showBreadcrumbs" aria-label="breadcrumb">
|
<nav *ngIf="(showBreadcrumbs$ | async)" aria-label="breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<ng-container
|
<ng-container
|
||||||
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
|
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
|
||||||
|
@@ -1,115 +1,78 @@
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
import { BreadcrumbsComponent } from './breadcrumbs.component';
|
import { BreadcrumbsComponent } from './breadcrumbs.component';
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { VarDirective } from '../shared/utils/var.directive';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
|
||||||
import { BreadcrumbConfig } from './breadcrumb/breadcrumb-config.model';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { BreadcrumbsService } from '../core/breadcrumbs/breadcrumbs.service';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { DebugElement } from '@angular/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|
||||||
import { VarDirective } from '../shared/utils/var.directive';
|
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
|
||||||
|
|
||||||
class TestBreadcrumbsService implements BreadcrumbsService<string> {
|
|
||||||
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
|
||||||
return observableOf([new Breadcrumb(key, url)]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('BreadcrumbsComponent', () => {
|
describe('BreadcrumbsComponent', () => {
|
||||||
let component: BreadcrumbsComponent;
|
let component: BreadcrumbsComponent;
|
||||||
let fixture: ComponentFixture<BreadcrumbsComponent>;
|
let fixture: ComponentFixture<BreadcrumbsComponent>;
|
||||||
let router: any;
|
let breadcrumbsServiceMock: BreadcrumbsService;
|
||||||
let route: any;
|
|
||||||
let breadcrumbProvider;
|
|
||||||
let breadcrumbConfigA: BreadcrumbConfig<string>;
|
|
||||||
let breadcrumbConfigB: BreadcrumbConfig<string>;
|
|
||||||
let expectedBreadcrumbs;
|
|
||||||
|
|
||||||
function init() {
|
const expectBreadcrumb = (listItem: DebugElement, text: string, url: string) => {
|
||||||
breadcrumbProvider = new TestBreadcrumbsService();
|
const anchor = listItem.query(By.css('a'));
|
||||||
|
|
||||||
breadcrumbConfigA = { provider: breadcrumbProvider, key: 'example.path', url: 'example.com' };
|
if (url == null) {
|
||||||
breadcrumbConfigB = { provider: breadcrumbProvider, key: 'another.path', url: 'another.com' };
|
expect(anchor).toBeNull();
|
||||||
|
expect(listItem.nativeElement.innerHTML).toEqual(text);
|
||||||
route = {
|
} else {
|
||||||
root: {
|
expect(anchor).toBeInstanceOf(DebugElement);
|
||||||
snapshot: {
|
expect(anchor.attributes.href).toEqual(url);
|
||||||
data: { breadcrumb: breadcrumbConfigA },
|
expect(anchor.nativeElement.innerHTML).toEqual(text);
|
||||||
routeConfig: { resolve: { breadcrumb: {} } }
|
|
||||||
},
|
|
||||||
firstChild: {
|
|
||||||
snapshot: {
|
|
||||||
// Example without resolver should be ignored
|
|
||||||
data: { breadcrumb: breadcrumbConfigA },
|
|
||||||
},
|
|
||||||
firstChild: {
|
|
||||||
snapshot: {
|
|
||||||
data: { breadcrumb: breadcrumbConfigB },
|
|
||||||
routeConfig: { resolve: { breadcrumb: {} } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
expectedBreadcrumbs = [
|
|
||||||
new Breadcrumb(breadcrumbConfigA.key, breadcrumbConfigA.url),
|
|
||||||
new Breadcrumb(breadcrumbConfigB.key, breadcrumbConfigB.url)
|
|
||||||
];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
breadcrumbsServiceMock = {
|
||||||
|
breadcrumbs$: observableOf([
|
||||||
|
// NOTE: a root breadcrumb is automatically rendered
|
||||||
|
new Breadcrumb('bc 1', 'example.com'),
|
||||||
|
new Breadcrumb('bc 2', 'another.com'),
|
||||||
|
]),
|
||||||
|
showBreadcrumbs$: observableOf(true),
|
||||||
|
} as BreadcrumbsService;
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [BreadcrumbsComponent, VarDirective],
|
declarations: [
|
||||||
imports: [RouterTestingModule.withRoutes([]), TranslateModule.forRoot({
|
BreadcrumbsComponent,
|
||||||
|
VarDirective,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule.withRoutes([]),
|
||||||
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
provide: TranslateLoader,
|
provide: TranslateLoader,
|
||||||
useClass: TranslateLoaderMock
|
useClass: TranslateLoaderMock,
|
||||||
}
|
}
|
||||||
}), NgbModule],
|
}),
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ActivatedRoute, useValue: route}
|
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceMock },
|
||||||
], schemas: [NO_ERRORS_SCHEMA]
|
],
|
||||||
})
|
}).compileComponents();
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(BreadcrumbsComponent);
|
fixture = TestBed.createComponent(BreadcrumbsComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
router = TestBed.inject(Router);
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
}));
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ngOnInit', () => {
|
it('should render the breadcrumbs', () => {
|
||||||
beforeEach(() => {
|
const breadcrumbs = fixture.debugElement.queryAll(By.css('.breadcrumb-item'));
|
||||||
spyOn(component, 'resolveBreadcrumbs').and.returnValue(observableOf([]));
|
expect(breadcrumbs.length).toBe(3);
|
||||||
|
expectBreadcrumb(breadcrumbs[0], 'home.breadcrumbs', '/');
|
||||||
|
expectBreadcrumb(breadcrumbs[1], 'bc 1', '/example.com');
|
||||||
|
expectBreadcrumb(breadcrumbs[2], 'bc 2', null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call resolveBreadcrumb on init', () => {
|
|
||||||
router.events = observableOf(new NavigationEnd(0, '', ''));
|
|
||||||
component.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
expect(component.resolveBreadcrumbs).toHaveBeenCalledWith(route.root);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('resolveBreadcrumbs', () => {
|
|
||||||
it('should return the correct breadcrumbs', () => {
|
|
||||||
const breadcrumbs = component.resolveBreadcrumbs(route.root);
|
|
||||||
getTestScheduler().expectObservable(breadcrumbs).toBe('(a|)', { a: expectedBreadcrumbs });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -1,9 +1,7 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
import { hasNoValue, hasValue, isUndefined } from '../shared/empty.util';
|
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the breadcrumbs of a page
|
* Component representing the breadcrumbs of a page
|
||||||
@@ -13,7 +11,8 @@ import { combineLatest, Observable, of as observableOf } from 'rxjs';
|
|||||||
templateUrl: './breadcrumbs.component.html',
|
templateUrl: './breadcrumbs.component.html',
|
||||||
styleUrls: ['./breadcrumbs.component.scss']
|
styleUrls: ['./breadcrumbs.component.scss']
|
||||||
})
|
})
|
||||||
export class BreadcrumbsComponent implements OnInit {
|
export class BreadcrumbsComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observable of the list of breadcrumbs for this page
|
* Observable of the list of breadcrumbs for this page
|
||||||
*/
|
*/
|
||||||
@@ -22,61 +21,13 @@ export class BreadcrumbsComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Whether or not to show breadcrumbs on this page
|
* Whether or not to show breadcrumbs on this page
|
||||||
*/
|
*/
|
||||||
showBreadcrumbs: boolean;
|
showBreadcrumbs$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private breadcrumbsService: BreadcrumbsService,
|
||||||
private router: Router
|
|
||||||
) {
|
) {
|
||||||
|
this.breadcrumbs$ = breadcrumbsService.breadcrumbs$;
|
||||||
|
this.showBreadcrumbs$ = breadcrumbsService.showBreadcrumbs$;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the breadcrumbs on init for this page
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.breadcrumbs$ = this.router.events.pipe(
|
|
||||||
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
|
||||||
tap(() => this.reset()),
|
|
||||||
switchMap(() => this.resolveBreadcrumbs(this.route.root)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method that recursively resolves breadcrumbs
|
|
||||||
* @param route The route to get the breadcrumb from
|
|
||||||
*/
|
|
||||||
resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> {
|
|
||||||
const data = route.snapshot.data;
|
|
||||||
const routeConfig = route.snapshot.routeConfig;
|
|
||||||
|
|
||||||
const last: boolean = hasNoValue(route.firstChild);
|
|
||||||
if (last) {
|
|
||||||
if (hasValue(data.showBreadcrumbs)) {
|
|
||||||
this.showBreadcrumbs = data.showBreadcrumbs;
|
|
||||||
} else if (isUndefined(data.breadcrumb)) {
|
|
||||||
this.showBreadcrumbs = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
hasValue(data) && hasValue(data.breadcrumb) &&
|
|
||||||
hasValue(routeConfig) && hasValue(routeConfig.resolve) && hasValue(routeConfig.resolve.breadcrumb)
|
|
||||||
) {
|
|
||||||
const { provider, key, url } = data.breadcrumb;
|
|
||||||
if (!last) {
|
|
||||||
return combineLatest(provider.getBreadcrumbs(key, url), this.resolveBreadcrumbs(route.firstChild))
|
|
||||||
.pipe(map((crumbs) => [].concat.apply([], crumbs)));
|
|
||||||
} else {
|
|
||||||
return provider.getBreadcrumbs(key, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the state of the breadcrumbs
|
|
||||||
*/
|
|
||||||
reset() {
|
|
||||||
this.showBreadcrumbs = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
171
src/app/breadcrumbs/breadcrumbs.service.spec.ts
Normal file
171
src/app/breadcrumbs/breadcrumbs.service.spec.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||||
|
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
|
||||||
|
import { Observable, of as observableOf, Subject } from 'rxjs';
|
||||||
|
import { BreadcrumbConfig } from './breadcrumb/breadcrumb-config.model';
|
||||||
|
import { BreadcrumbsProviderService } from '../core/breadcrumbs/breadcrumbsProviderService';
|
||||||
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
|
class TestBreadcrumbsService implements BreadcrumbsProviderService<string> {
|
||||||
|
getBreadcrumbs(key: string, url: string): Observable<Breadcrumb[]> {
|
||||||
|
return observableOf([new Breadcrumb(key, url)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BreadcrumbsService', () => {
|
||||||
|
let service: BreadcrumbsService;
|
||||||
|
let routerEventsObs: Subject<any>;
|
||||||
|
let routerMock: Router;
|
||||||
|
let activatedRouteMock: Partial<ActivatedRoute>;
|
||||||
|
let currentRootRoute: Partial<ActivatedRoute>;
|
||||||
|
let breadcrumbProvider;
|
||||||
|
let breadcrumbConfigA: BreadcrumbConfig<string>;
|
||||||
|
let breadcrumbConfigB: BreadcrumbConfig<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init breadcrumb variables, see beforeEach
|
||||||
|
*/
|
||||||
|
const initBreadcrumbs = () => {
|
||||||
|
breadcrumbProvider = new TestBreadcrumbsService();
|
||||||
|
breadcrumbConfigA = { provider: breadcrumbProvider, key: 'example.path', url: 'example.com' };
|
||||||
|
breadcrumbConfigB = { provider: breadcrumbProvider, key: 'another.path', url: 'another.com' };
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeActivatedRoute = (newRootRoute: any) => {
|
||||||
|
// update the ActivatedRoute that the service will receive
|
||||||
|
currentRootRoute = newRootRoute;
|
||||||
|
|
||||||
|
// the pipeline of BreadcrumbsService#listenForRouteChanges needs a NavigationEnd event,
|
||||||
|
// but the actual payload does not matter, since ActivatedRoute is mocked too.
|
||||||
|
routerEventsObs.next(new NavigationEnd(0, '', ''));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
initBreadcrumbs();
|
||||||
|
|
||||||
|
routerEventsObs = new Subject<any>();
|
||||||
|
|
||||||
|
// BreadcrumbsService uses Router#events
|
||||||
|
routerMock = jasmine.createSpyObj([], {
|
||||||
|
events: routerEventsObs,
|
||||||
|
});
|
||||||
|
|
||||||
|
// BreadcrumbsService uses ActivatedRoute#root
|
||||||
|
activatedRouteMock = {
|
||||||
|
get root() {
|
||||||
|
return currentRootRoute as ActivatedRoute;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{ provide: Router, useValue: routerMock },
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRouteMock },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(BreadcrumbsService);
|
||||||
|
|
||||||
|
// this is done by AppComponent under regular circumstances
|
||||||
|
service.listenForRouteChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('breadcrumbs$', () => {
|
||||||
|
it('should return a breadcrumb corresponding to the current route', () => {
|
||||||
|
const route1 = {
|
||||||
|
snapshot: {
|
||||||
|
data: { breadcrumb: breadcrumbConfigA },
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectation1 = [
|
||||||
|
new Breadcrumb(breadcrumbConfigA.key, breadcrumbConfigA.url),
|
||||||
|
];
|
||||||
|
|
||||||
|
changeActivatedRoute(route1);
|
||||||
|
expect(service.breadcrumbs$).toBeObservable(cold('a', { a: expectation1 }));
|
||||||
|
|
||||||
|
const route2 = {
|
||||||
|
snapshot: {
|
||||||
|
data: { breadcrumb: breadcrumbConfigA },
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
},
|
||||||
|
firstChild: {
|
||||||
|
snapshot: {
|
||||||
|
// Example without resolver should be ignored
|
||||||
|
data: { breadcrumb: breadcrumbConfigA },
|
||||||
|
},
|
||||||
|
firstChild: {
|
||||||
|
snapshot: {
|
||||||
|
data: { breadcrumb: breadcrumbConfigB },
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const expectation2 = [
|
||||||
|
new Breadcrumb(breadcrumbConfigA.key, breadcrumbConfigA.url),
|
||||||
|
new Breadcrumb(breadcrumbConfigB.key, breadcrumbConfigB.url),
|
||||||
|
];
|
||||||
|
|
||||||
|
changeActivatedRoute(route2);
|
||||||
|
expect(service.breadcrumbs$).toBeObservable(cold('a', { a: expectation2 }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('showBreadcrumbs$', () => {
|
||||||
|
describe('when the last part of the route has showBreadcrumbs in its data', () => {
|
||||||
|
it('should return that value', () => {
|
||||||
|
const route1 = {
|
||||||
|
snapshot: {
|
||||||
|
data: {
|
||||||
|
breadcrumb: breadcrumbConfigA,
|
||||||
|
showBreadcrumbs: false, // explicitly hide breadcrumbs
|
||||||
|
},
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeActivatedRoute(route1);
|
||||||
|
expect(service.showBreadcrumbs$).toBeObservable(cold('a', { a: false }));
|
||||||
|
|
||||||
|
const route2 = {
|
||||||
|
snapshot: {
|
||||||
|
data: {
|
||||||
|
breadcrumb: breadcrumbConfigA,
|
||||||
|
showBreadcrumbs: true, // explicitly show breadcrumbs
|
||||||
|
},
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeActivatedRoute(route2);
|
||||||
|
expect(service.showBreadcrumbs$).toBeObservable(cold('a', { a: true }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the last part of the route has no breadcrumb in its data', () => {
|
||||||
|
it('should return false', () => {
|
||||||
|
const route1 = {
|
||||||
|
snapshot: {
|
||||||
|
data: {
|
||||||
|
// no breadcrumbs set - always hide
|
||||||
|
},
|
||||||
|
routeConfig: { resolve: { breadcrumb: {} } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeActivatedRoute(route1);
|
||||||
|
expect(service.showBreadcrumbs$).toBeObservable(cold('a', { a: false }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
80
src/app/breadcrumbs/breadcrumbs.service.ts
Normal file
80
src/app/breadcrumbs/breadcrumbs.service.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {combineLatest, Observable, of as observableOf, ReplaySubject} from 'rxjs';
|
||||||
|
import {Breadcrumb} from './breadcrumb/breadcrumb.model';
|
||||||
|
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
|
||||||
|
import {filter, map, switchMap, tap} from 'rxjs/operators';
|
||||||
|
import {hasNoValue, hasValue, isUndefined} from '../shared/empty.util';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class BreadcrumbsService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable of the list of breadcrumbs for this page
|
||||||
|
*/
|
||||||
|
breadcrumbs$: ReplaySubject<Breadcrumb[]> = new ReplaySubject(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not to show breadcrumbs on this page
|
||||||
|
*/
|
||||||
|
showBreadcrumbs$: ReplaySubject<boolean> = new ReplaySubject(1);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by {@link AppComponent#constructor} (i.e. before routing)
|
||||||
|
* such that no routing events are missed.
|
||||||
|
*/
|
||||||
|
listenForRouteChanges() {
|
||||||
|
// supply events to this.breadcrumbs$
|
||||||
|
this.router.events.pipe(
|
||||||
|
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
|
||||||
|
tap(() => this.reset()),
|
||||||
|
switchMap(() => this.resolveBreadcrumbs(this.route.root)),
|
||||||
|
).subscribe(this.breadcrumbs$);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that recursively resolves breadcrumbs
|
||||||
|
* @param route The route to get the breadcrumb from
|
||||||
|
*/
|
||||||
|
private resolveBreadcrumbs(route: ActivatedRoute): Observable<Breadcrumb[]> {
|
||||||
|
const data = route.snapshot.data;
|
||||||
|
const routeConfig = route.snapshot.routeConfig;
|
||||||
|
|
||||||
|
const last: boolean = hasNoValue(route.firstChild);
|
||||||
|
if (last) {
|
||||||
|
if (hasValue(data.showBreadcrumbs)) {
|
||||||
|
this.showBreadcrumbs$.next(data.showBreadcrumbs);
|
||||||
|
} else if (isUndefined(data.breadcrumb)) {
|
||||||
|
this.showBreadcrumbs$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
hasValue(data) && hasValue(data.breadcrumb) &&
|
||||||
|
hasValue(routeConfig) && hasValue(routeConfig.resolve) && hasValue(routeConfig.resolve.breadcrumb)
|
||||||
|
) {
|
||||||
|
const { provider, key, url } = data.breadcrumb;
|
||||||
|
if (!last) {
|
||||||
|
return combineLatest(provider.getBreadcrumbs(key, url), this.resolveBreadcrumbs(route.firstChild))
|
||||||
|
.pipe(map((crumbs) => [].concat.apply([], crumbs)));
|
||||||
|
} else {
|
||||||
|
return provider.getBreadcrumbs(key, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !last ? this.resolveBreadcrumbs(route.firstChild) : observableOf([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the state of the breadcrumbs
|
||||||
|
*/
|
||||||
|
private reset() {
|
||||||
|
this.showBreadcrumbs$.next(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
25
src/app/breadcrumbs/themed-breadcrumbs.component.ts
Normal file
25
src/app/breadcrumbs/themed-breadcrumbs.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { BreadcrumbsComponent } from './breadcrumbs.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BreadcrumbsComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-breadcrumbs',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedBreadcrumbsComponent extends ThemedComponent<BreadcrumbsComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BreadcrumbsComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/breadcrumbs/breadcrumbs.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./breadcrumbs.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,14 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { CommunityListPageComponent } from './community-list-page.component';
|
import { CommunityListPageComponent } from './community-list-page.component';
|
||||||
import { CommunityListPageRoutingModule } from './community-list-page.routing.module';
|
import { CommunityListPageRoutingModule } from './community-list-page.routing.module';
|
||||||
import { CommunityListComponent } from './community-list/community-list.component';
|
import { CommunityListComponent } from './community-list/community-list.component';
|
||||||
|
import { ThemedCommunityListPageComponent } from './themed-community-list-page.component';
|
||||||
|
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
CommunityListPageComponent,
|
||||||
|
CommunityListComponent,
|
||||||
|
ThemedCommunityListPageComponent
|
||||||
|
];
|
||||||
/**
|
/**
|
||||||
* The page which houses a title and the community list, as described in community-list.component
|
* The page which houses a title and the community list, as described in community-list.component
|
||||||
*/
|
*/
|
||||||
@@ -15,9 +22,11 @@ import { CommunityListComponent } from './community-list/community-list.componen
|
|||||||
CommunityListPageRoutingModule
|
CommunityListPageRoutingModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CommunityListPageComponent,
|
...DECLARATIONS
|
||||||
CommunityListComponent
|
],
|
||||||
]
|
exports: [
|
||||||
|
...DECLARATIONS,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class CommunityListPageModule {
|
export class CommunityListPageModule {
|
||||||
|
|
||||||
|
@@ -2,8 +2,8 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||||
|
|
||||||
import { CommunityListPageComponent } from './community-list-page.component';
|
|
||||||
import { CommunityListService } from './community-list-service';
|
import { CommunityListService } from './community-list-service';
|
||||||
|
import { ThemedCommunityListPageComponent } from './themed-community-list-page.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RouterModule to help navigate to the page with the community list tree
|
* RouterModule to help navigate to the page with the community list tree
|
||||||
@@ -13,7 +13,7 @@ import { CommunityListService } from './community-list-service';
|
|||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CommunityListPageComponent,
|
component: ThemedCommunityListPageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
data: { title: 'communityList.tabTitle' }
|
data: { title: 'communityList.tabTitle' }
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { CommunityListPageComponent } from './community-list-page.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for CommunityListPageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-community-list-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedCommunityListPageComponent extends ThemedComponent<CommunityListPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'CommunityListPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/community-list-page/community-list-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./community-list-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -4,7 +4,7 @@ import { Observable } from 'rxjs';
|
|||||||
/**
|
/**
|
||||||
* Service to calculate breadcrumbs for a single part of the route
|
* Service to calculate breadcrumbs for a single part of the route
|
||||||
*/
|
*/
|
||||||
export interface BreadcrumbsService<T> {
|
export interface BreadcrumbsProviderService<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to calculate the breadcrumbs for a part of the route
|
* Method to calculate the breadcrumbs for a part of the route
|
@@ -1,5 +1,5 @@
|
|||||||
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||||
import { BreadcrumbsService } from './breadcrumbs.service';
|
import { BreadcrumbsProviderService } from './breadcrumbsProviderService';
|
||||||
import { DSONameService } from './dso-name.service';
|
import { DSONameService } from './dso-name.service';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { ChildHALResource } from '../shared/child-hal-resource.model';
|
import { ChildHALResource } from '../shared/child-hal-resource.model';
|
||||||
@@ -18,7 +18,7 @@ import { getDSORoute } from '../../app-routing-paths';
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResource & DSpaceObject> {
|
export class DSOBreadcrumbsService implements BreadcrumbsProviderService<ChildHALResource & DSpaceObject> {
|
||||||
constructor(
|
constructor(
|
||||||
private linkService: LinkService,
|
private linkService: LinkService,
|
||||||
private dsoNameService: DSONameService
|
private dsoNameService: DSONameService
|
||||||
|
@@ -45,7 +45,7 @@ describe(`DSONameService`, () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
service = new DSONameService();
|
service = new DSONameService({ instant: (a) => a } as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(`getName`, () => {
|
describe(`getName`, () => {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a name for a {@link DSpaceObject} based
|
* Returns a name for a {@link DSpaceObject} based
|
||||||
@@ -11,6 +12,10 @@ import { DSpaceObject } from '../shared/dspace-object.model';
|
|||||||
})
|
})
|
||||||
export class DSONameService {
|
export class DSONameService {
|
||||||
|
|
||||||
|
constructor(private translateService: TranslateService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions to generate the specific names.
|
* Functions to generate the specific names.
|
||||||
*
|
*
|
||||||
@@ -29,7 +34,7 @@ export class DSONameService {
|
|||||||
},
|
},
|
||||||
Default: (dso: DSpaceObject): string => {
|
Default: (dso: DSpaceObject): string => {
|
||||||
// If object doesn't have dc.title metadata use name property
|
// If object doesn't have dc.title metadata use name property
|
||||||
return dso.firstMetadataValue('dc.title') || dso.name;
|
return dso.firstMetadataValue('dc.title') || dso.name || this.translateService.instant('dso.name.untitled');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
|
||||||
import { BreadcrumbsService } from './breadcrumbs.service';
|
import { BreadcrumbsProviderService } from './breadcrumbsProviderService';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export const BREADCRUMB_MESSAGE_POSTFIX = '.breadcrumbs';
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class I18nBreadcrumbsService implements BreadcrumbsService<string> {
|
export class I18nBreadcrumbsService implements BreadcrumbsProviderService<string> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to calculate the breadcrumbs
|
* Method to calculate the breadcrumbs
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { cold, getTestScheduler, hot } from 'jasmine-marbles';
|
import { cold } from 'jasmine-marbles';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
||||||
@@ -13,16 +13,15 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
|
|||||||
import { ComColDataService } from './comcol-data.service';
|
import { ComColDataService } from './comcol-data.service';
|
||||||
import { CommunityDataService } from './community-data.service';
|
import { CommunityDataService } from './community-data.service';
|
||||||
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
|
||||||
import { FindListOptions, GetRequest } from './request.models';
|
import { FindListOptions } from './request.models';
|
||||||
import { RequestEntry } from './request.reducer';
|
|
||||||
import { RequestService } from './request.service';
|
import { RequestService } from './request.service';
|
||||||
import {
|
import {
|
||||||
createFailedRemoteDataObject$,
|
createFailedRemoteDataObject$,
|
||||||
createNoContentRemoteDataObject$,
|
createSuccessfulRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject$
|
createFailedRemoteDataObject,
|
||||||
|
createSuccessfulRemoteDataObject
|
||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { BitstreamDataService } from './bitstream-data.service';
|
import { BitstreamDataService } from './bitstream-data.service';
|
||||||
import { take } from 'rxjs/operators';
|
|
||||||
|
|
||||||
const LINK_NAME = 'test';
|
const LINK_NAME = 'test';
|
||||||
|
|
||||||
@@ -50,8 +49,8 @@ class TestService extends ComColDataService<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tslint:disable:no-shadowed-variable
|
||||||
describe('ComColDataService', () => {
|
describe('ComColDataService', () => {
|
||||||
let scheduler: TestScheduler;
|
|
||||||
let service: TestService;
|
let service: TestService;
|
||||||
let requestService: RequestService;
|
let requestService: RequestService;
|
||||||
let cds: CommunityDataService;
|
let cds: CommunityDataService;
|
||||||
@@ -59,6 +58,8 @@ describe('ComColDataService', () => {
|
|||||||
let halService: any = {};
|
let halService: any = {};
|
||||||
let bitstreamDataService: BitstreamDataService;
|
let bitstreamDataService: BitstreamDataService;
|
||||||
let rdbService: RemoteDataBuildService;
|
let rdbService: RemoteDataBuildService;
|
||||||
|
let testScheduler: TestScheduler;
|
||||||
|
let topEndpoint: string;
|
||||||
|
|
||||||
const store = {} as Store<CoreState>;
|
const store = {} as Store<CoreState>;
|
||||||
const notificationsService = {} as NotificationsService;
|
const notificationsService = {} as NotificationsService;
|
||||||
@@ -69,17 +70,9 @@ describe('ComColDataService', () => {
|
|||||||
const options = Object.assign(new FindListOptions(), {
|
const options = Object.assign(new FindListOptions(), {
|
||||||
scopeID: scopeID
|
scopeID: scopeID
|
||||||
});
|
});
|
||||||
const getRequestEntry$ = (successful: boolean) => {
|
|
||||||
return observableOf({
|
|
||||||
response: { isSuccessful: successful } as any
|
|
||||||
} as RequestEntry);
|
|
||||||
};
|
|
||||||
|
|
||||||
const communitiesEndpoint = 'https://rest.api/core/communities';
|
const communitiesEndpoint = 'https://rest.api/core/communities';
|
||||||
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
|
const communityEndpoint = `${communitiesEndpoint}/${scopeID}`;
|
||||||
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
|
const scopedEndpoint = `${communityEndpoint}/${LINK_NAME}`;
|
||||||
const serviceEndpoint = `https://rest.api/core/${LINK_NAME}`;
|
|
||||||
const authHeader = 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJlaWQiOiJhNjA4NmIzNC0zOTE4LTQ1YjctOGRkZC05MzI5YTcwMmEyNmEiLCJzZyI6W10sImV4cCI6MTUzNDk0MDcyNX0.RV5GAtiX6cpwBN77P_v16iG9ipeyiO7faNYSNMzq_sQ';
|
|
||||||
|
|
||||||
const mockHalService = {
|
const mockHalService = {
|
||||||
getEndpoint: (linkPath) => observableOf(communitiesEndpoint)
|
getEndpoint: (linkPath) => observableOf(communitiesEndpoint)
|
||||||
@@ -98,8 +91,8 @@ describe('ComColDataService', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initMockCommunityDataService(): CommunityDataService {
|
function initMockCommunityDataService(): CommunityDataService {
|
||||||
return jasmine.createSpyObj('responseCache', {
|
return jasmine.createSpyObj('cds', {
|
||||||
getEndpoint: hot('--a-', { a: communitiesEndpoint }),
|
getEndpoint: cold('--a-', { a: communitiesEndpoint }),
|
||||||
getIDHref: communityEndpoint
|
getIDHref: communityEndpoint
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -134,7 +127,15 @@ describe('ComColDataService', () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initTestScheduler = (): TestScheduler => {
|
||||||
|
return new TestScheduler((actual, expected) => {
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
topEndpoint = 'https://rest.api/core/communities/search/top';
|
||||||
|
testScheduler = initTestScheduler();
|
||||||
cds = initMockCommunityDataService();
|
cds = initMockCommunityDataService();
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
objectCache = initMockObjectCacheService();
|
objectCache = initMockObjectCacheService();
|
||||||
@@ -145,82 +146,76 @@ describe('ComColDataService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseEndpoint', () => {
|
describe('getBrowseEndpoint', () => {
|
||||||
beforeEach(() => {
|
it(`should call createAndSendGetRequest with the scope Community's self link`, () => {
|
||||||
scheduler = getTestScheduler();
|
testScheduler.run(({ cold, flush, expectObservable }) => {
|
||||||
|
(cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint }));
|
||||||
|
(rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() }));
|
||||||
|
spyOn(service as any, 'createAndSendGetRequest');
|
||||||
|
service.getBrowseEndpoint(options);
|
||||||
|
flush();
|
||||||
|
expectObservable((service as any).createAndSendGetRequest.calls.argsFor(0)[0]).toBe('(a|)', { a: communityEndpoint });
|
||||||
|
expect((service as any).createAndSendGetRequest.calls.argsFor(0)[1]).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send a new FindByIDRequest for the scope Community', () => {
|
|
||||||
cds = initMockCommunityDataService();
|
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
|
||||||
objectCache = initMockObjectCacheService();
|
|
||||||
service = initTestService();
|
|
||||||
|
|
||||||
const expected = new GetRequest(requestService.generateRequestId(), communityEndpoint);
|
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseEndpoint(options).subscribe());
|
|
||||||
scheduler.flush();
|
|
||||||
|
|
||||||
expect(requestService.send).toHaveBeenCalledWith(expected, true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('if the scope Community can\'t be found', () => {
|
describe('if the scope Community can\'t be found', () => {
|
||||||
it('should throw an error', () => {
|
it('should throw an error', () => {
|
||||||
const result = service.getBrowseEndpoint(options).pipe(take(1));
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
const expected = cold('--#-', undefined, new Error(`The Community with scope ${scopeID} couldn't be retrieved`));
|
// spies re-defined here to use the "cold" function from rxjs's TestScheduler
|
||||||
|
// rather than the one imported from jasmine-marbles.
|
||||||
expect(result).toBeObservable(expected);
|
// Mixing the two seems to lead to unpredictable results
|
||||||
|
(cds.getEndpoint as jasmine.Spy).and.returnValue(cold('a', { a: communitiesEndpoint }));
|
||||||
|
(rdbService.buildSingle as jasmine.Spy).and.returnValue(cold('a', { a: createFailedRemoteDataObject() }));
|
||||||
|
const expectedError = new Error(`The Community with scope ${scopeID} couldn't be retrieved`);
|
||||||
|
expectObservable(service.getBrowseEndpoint(options)).toBe('#', undefined, expectedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('cache refresh', () => {
|
describe('cache refresh', () => {
|
||||||
let communityWithoutParentHref;
|
let communityWithoutParentHref;
|
||||||
let data;
|
let communityWithParentHref;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(halService, 'getEndpoint').and.returnValue(observableOf('https://rest.api/core/communities/search/top'));
|
communityWithParentHref = {
|
||||||
});
|
|
||||||
|
|
||||||
describe('cache refreshed top level community', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
(rdbService.buildSingle as jasmine.Spy).and.returnValue(createNoContentRemoteDataObject$());
|
|
||||||
data = {
|
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'top level community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {
|
_links: {
|
||||||
parentCommunity: {
|
parentCommunity: {
|
||||||
href: 'topLevel/parentCommunity'
|
href: 'topLevel/parentCommunity'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
} as Community;
|
||||||
communityWithoutParentHref = {
|
communityWithoutParentHref = {
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'top level community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {}
|
_links: {}
|
||||||
};
|
} as Community;
|
||||||
});
|
});
|
||||||
it('top level community cache refreshed', () => {
|
|
||||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
describe('cache refreshed top level community', () => {
|
||||||
scheduler.flush();
|
it(`should refresh the top level community cache when the dso has a parent link that can't be resolved`, () => {
|
||||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('https://rest.api/core/communities/search/top');
|
testScheduler.run(({ flush, cold }) => {
|
||||||
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) }));
|
||||||
|
service.refreshCache(communityWithParentHref);
|
||||||
|
flush();
|
||||||
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(topEndpoint);
|
||||||
});
|
});
|
||||||
it('top level community without parent link, cache not refreshed', () => {
|
});
|
||||||
scheduler.schedule(() => (service as any).refreshCache(communityWithoutParentHref));
|
it(`shouldn't do anything when the dso doesn't have a parent link`, () => {
|
||||||
scheduler.flush();
|
testScheduler.run(({ flush, cold }) => {
|
||||||
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject({}) }));
|
||||||
|
service.refreshCache(communityWithoutParentHref);
|
||||||
|
flush();
|
||||||
expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalled();
|
expect(requestService.setStaleByHrefSubstring).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('cache refreshed child community', () => {
|
describe('cache refreshed child community', () => {
|
||||||
|
let parentCommunity: Community;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const parentCommunity = Object.assign(new Community(), {
|
parentCommunity = Object.assign(new Community(), {
|
||||||
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
uuid: 'a20da287-e174-466a-9926-f66as300d399',
|
||||||
id: 'a20da287-e174-466a-9926-f66as300d399',
|
id: 'a20da287-e174-466a-9926-f66as300d399',
|
||||||
metadata: [{
|
metadata: [{
|
||||||
@@ -229,29 +224,16 @@ describe('ComColDataService', () => {
|
|||||||
}],
|
}],
|
||||||
_links: {}
|
_links: {}
|
||||||
});
|
});
|
||||||
(rdbService.buildSingle as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$(parentCommunity));
|
|
||||||
data = {
|
|
||||||
dso: Object.assign(new Community(), {
|
|
||||||
metadata: [{
|
|
||||||
key: 'dc.title',
|
|
||||||
value: 'child community'
|
|
||||||
}]
|
|
||||||
}),
|
|
||||||
_links: {
|
|
||||||
parentCommunity: {
|
|
||||||
href: 'child/parentCommunity'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
it('child level community cache refreshed', () => {
|
it('should refresh a specific cached community when the parent link can be resolved', () => {
|
||||||
scheduler.schedule(() => (service as any).refreshCache(data));
|
testScheduler.run(({ flush, cold }) => {
|
||||||
scheduler.flush();
|
spyOn(halService, 'getEndpoint').and.returnValue(cold('a', { a: topEndpoint }));
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: createSuccessfulRemoteDataObject(parentCommunity) }));
|
||||||
|
service.refreshCache(communityWithParentHref);
|
||||||
|
flush();
|
||||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith('a20da287-e174-466a-9926-f66as300d399');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -20,6 +20,9 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
|||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||||
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
import { RemoteData } from './remote-data';
|
||||||
|
import { RequestEntryState } from './request.reducer';
|
||||||
|
|
||||||
const endpoint = 'https://rest.api/core';
|
const endpoint = 'https://rest.api/core';
|
||||||
|
|
||||||
@@ -63,6 +66,10 @@ describe('DataService', () => {
|
|||||||
let comparator;
|
let comparator;
|
||||||
let objectCache;
|
let objectCache;
|
||||||
let store;
|
let store;
|
||||||
|
let selfLink;
|
||||||
|
let linksToFollow;
|
||||||
|
let testScheduler;
|
||||||
|
let remoteDataMocks;
|
||||||
|
|
||||||
function initTestService(): TestService {
|
function initTestService(): TestService {
|
||||||
requestService = getMockRequestService();
|
requestService = getMockRequestService();
|
||||||
@@ -81,6 +88,34 @@ describe('DataService', () => {
|
|||||||
}
|
}
|
||||||
} as any;
|
} as any;
|
||||||
store = {} as Store<CoreState>;
|
store = {} as Store<CoreState>;
|
||||||
|
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
||||||
|
linksToFollow = [
|
||||||
|
followLink('a'),
|
||||||
|
followLink('b')
|
||||||
|
];
|
||||||
|
|
||||||
|
testScheduler = new TestScheduler((actual, expected) => {
|
||||||
|
// asserting the two objects are equal
|
||||||
|
// e.g. using chai.
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const timeStamp = new Date().getTime();
|
||||||
|
const msToLive = 15 * 60 * 1000;
|
||||||
|
const payload = { foo: 'bar' };
|
||||||
|
const statusCodeSuccess = 200;
|
||||||
|
const statusCodeError = 404;
|
||||||
|
const errorMessage = 'not found';
|
||||||
|
remoteDataMocks = {
|
||||||
|
RequestPending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.RequestPending, undefined, undefined, undefined),
|
||||||
|
ResponsePending: new RemoteData(undefined, msToLive, timeStamp, RequestEntryState.ResponsePending, undefined, undefined, undefined),
|
||||||
|
Success: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Success, undefined, payload, statusCodeSuccess),
|
||||||
|
SuccessStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.SuccessStale, undefined, payload, statusCodeSuccess),
|
||||||
|
Error: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.Error, errorMessage, undefined, statusCodeError),
|
||||||
|
ErrorStale: new RemoteData(timeStamp, msToLive, timeStamp, RequestEntryState.ErrorStale, errorMessage, undefined, statusCodeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return new TestService(
|
return new TestService(
|
||||||
requestService,
|
requestService,
|
||||||
rdbService,
|
rdbService,
|
||||||
@@ -307,14 +342,12 @@ describe('DataService', () => {
|
|||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
let operations;
|
let operations;
|
||||||
let selfLink;
|
|
||||||
let dso;
|
let dso;
|
||||||
let dso2;
|
let dso2;
|
||||||
const name1 = 'random string';
|
const name1 = 'random string';
|
||||||
const name2 = 'another random string';
|
const name2 = 'another random string';
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation];
|
operations = [{ op: 'replace', path: '/0/value', value: name2 } as Operation];
|
||||||
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
|
|
||||||
|
|
||||||
dso = Object.assign(new DSpaceObject(), {
|
dso = Object.assign(new DSpaceObject(), {
|
||||||
_links: { self: { href: selfLink } },
|
_links: { self: { href: selfLink } },
|
||||||
@@ -340,5 +373,452 @@ describe('DataService', () => {
|
|||||||
expect(objectCache.addPatch).not.toHaveBeenCalled();
|
expect(objectCache.addPatch).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe(`reRequestStaleRemoteData`, () => {
|
||||||
|
let callback: jasmine.Spy<jasmine.Func>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
callback = jasmine.createSpy();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe(`when shouldReRequest is false`, () => {
|
||||||
|
it(`shouldn't do anything`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b-c-d-e-f';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.RequestPending,
|
||||||
|
b: remoteDataMocks.ResponsePending,
|
||||||
|
c: remoteDataMocks.Success,
|
||||||
|
d: remoteDataMocks.SuccessStale,
|
||||||
|
e: remoteDataMocks.Error,
|
||||||
|
f: remoteDataMocks.ErrorStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(false, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when shouldReRequest is true`, () => {
|
||||||
|
it(`should call the callback for stale RemoteData objects, but still pass the source observable unmodified`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.ErrorStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should only call the callback for stale RemoteData objects if something is subscribed to it`, (done) => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result$ = (service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values));
|
||||||
|
expectObservable(result$).toBe(expected, values);
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
result$.subscribe(() => {
|
||||||
|
expect(callback).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`shouldn't do anything for RemoteData objects that aren't stale`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable, flush }) => {
|
||||||
|
const expected = 'a-b-c-d';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.RequestPending,
|
||||||
|
b: remoteDataMocks.ResponsePending,
|
||||||
|
c: remoteDataMocks.Success,
|
||||||
|
d: remoteDataMocks.Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable((service as any).reRequestStaleRemoteData(true, callback)(cold(expected, values))).toBe(expected, values);
|
||||||
|
// since the callback happens in a tap(), flush to ensure it has been executed
|
||||||
|
flush();
|
||||||
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`findByHref`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, {}, [], ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
|
||||||
|
service.findByHref(selfLink, false, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call rdbService.buildSingle with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect(rdbService.buildSingle).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
||||||
|
expectObservable(rdbService.buildSingle.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: 'bingo!',
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findByHref call as a callback`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
|
||||||
|
service.findByHref(selfLink, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
||||||
|
spyOn(service, 'findByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
// prove that the spy we just added hasn't been called yet
|
||||||
|
expect(service.findByHref).not.toHaveBeenCalled();
|
||||||
|
// call the callback passed to reRequestStaleRemoteData
|
||||||
|
(service as any).reRequestStaleRemoteData.calls.argsFor(0)[1]();
|
||||||
|
// verify that findByHref _has_ been called now, with the same params as the original call
|
||||||
|
expect(service.findByHref).toHaveBeenCalledWith(jasmine.anything(), true, true, ...linksToFollow);
|
||||||
|
// ... except for selflink, which will have been turned in to an observable.
|
||||||
|
expectObservable((service.findByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is true`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = 'a-b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is false`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildSingle').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findByHref(selfLink, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`findAllByHref`, () => {
|
||||||
|
let findListOptions;
|
||||||
|
beforeEach(() => {
|
||||||
|
findListOptions = { currentPage: 5 };
|
||||||
|
spyOn(service as any, 'createAndSendGetRequest').and.callFake((href$) => { href$.subscribe().unsubscribe(); });
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call buildHrefFromFindOptions with href and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect(service.buildHrefFromFindOptions).toHaveBeenCalledWith(selfLink, findListOptions, [], ...linksToFollow);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call createAndSendGetRequest with the result from buildHrefFromFindOptions and useCachedVersionIfAvailable`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), true);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow);
|
||||||
|
expect((service as any).createAndSendGetRequest).toHaveBeenCalledWith(jasmine.anything(), false);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(1)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call rdbService.buildList with the result from buildHrefFromFindOptions and linksToFollow`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect(rdbService.buildList).toHaveBeenCalledWith(jasmine.anything() as any, ...linksToFollow);
|
||||||
|
expectObservable(rdbService.buildList.calls.argsFor(0)[0]).toBe('(a|)', { a: 'bingo!' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should call reRequestStaleRemoteData with reRequestOnStale and the exact same findAllByHref call as a callback`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue('bingo!');
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
|
||||||
|
service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow);
|
||||||
|
expect((service as any).reRequestStaleRemoteData.calls.argsFor(0)[0]).toBeTrue();
|
||||||
|
spyOn(service, 'findAllByHref').and.returnValue(cold('a', { a: remoteDataMocks.SuccessStale }));
|
||||||
|
// prove that the spy we just added hasn't been called yet
|
||||||
|
expect(service.findAllByHref).not.toHaveBeenCalled();
|
||||||
|
// call the callback passed to reRequestStaleRemoteData
|
||||||
|
(service as any).reRequestStaleRemoteData.calls.argsFor(0)[1]();
|
||||||
|
// verify that findAllByHref _has_ been called now, with the same params as the original call
|
||||||
|
expect(service.findAllByHref).toHaveBeenCalledWith(jasmine.anything(), findListOptions, true, true, ...linksToFollow);
|
||||||
|
// ... except for selflink, which will have been turned in to an observable.
|
||||||
|
expectObservable((service.findAllByHref as jasmine.Spy).calls.argsFor(0)[0]).toBe('(a|)', { a: selfLink });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return a the output from reRequestStaleRemoteData`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a', { a: remoteDataMocks.Success }));
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.returnValue(() => cold('a', { a: 'bingo!' }));
|
||||||
|
const expected = 'a';
|
||||||
|
const values = {
|
||||||
|
a: 'bingo!',
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is true`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should emit a cached completed RemoteData immediately, and keep emitting if it gets rerequested`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = 'a-b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, true, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`when useCachedVersionIfAvailable is false`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'buildHrefFromFindOptions').and.returnValue(selfLink);
|
||||||
|
spyOn(service as any, 'reRequestStaleRemoteData').and.callFake(() => (source) => source);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it(`should not emit a cached completed RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.Success,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should not emit a cached stale RemoteData, but only start emitting after the state first changes to RequestPending`, () => {
|
||||||
|
testScheduler.run(({ cold, expectObservable }) => {
|
||||||
|
spyOn(rdbService, 'buildList').and.returnValue(cold('a-b-c-d-e', {
|
||||||
|
a: remoteDataMocks.SuccessStale,
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
}));
|
||||||
|
const expected = '--b-c-d-e';
|
||||||
|
const values = {
|
||||||
|
b: remoteDataMocks.RequestPending,
|
||||||
|
c: remoteDataMocks.ResponsePending,
|
||||||
|
d: remoteDataMocks.Success,
|
||||||
|
e: remoteDataMocks.SuccessStale,
|
||||||
|
};
|
||||||
|
|
||||||
|
expectObservable(service.findAllByHref(selfLink, findListOptions, false, true, ...linksToFollow)).toBe(expected, values);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -12,6 +12,7 @@ import {
|
|||||||
takeWhile,
|
takeWhile,
|
||||||
switchMap,
|
switchMap,
|
||||||
tap,
|
tap,
|
||||||
|
skipWhile,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
|
||||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||||
@@ -45,29 +46,6 @@ import { UpdateDataService } from './update-data.service';
|
|||||||
import { GenericConstructor } from '../shared/generic-constructor';
|
import { GenericConstructor } from '../shared/generic-constructor';
|
||||||
import { NoContent } from '../shared/NoContent.model';
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
|
|
||||||
/**
|
|
||||||
* An operator that will call the given function if the incoming RemoteData is stale and
|
|
||||||
* shouldReRequest is true
|
|
||||||
*
|
|
||||||
* @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale
|
|
||||||
* @param requestFn The function to call if the RemoteData is stale and shouldReRequest is
|
|
||||||
* true
|
|
||||||
*/
|
|
||||||
export const reRequestStaleRemoteData = <T>(shouldReRequest: boolean, requestFn: () => Observable<RemoteData<T>>) =>
|
|
||||||
(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => {
|
|
||||||
if (shouldReRequest === true) {
|
|
||||||
return source.pipe(
|
|
||||||
tap((remoteData: RemoteData<T>) => {
|
|
||||||
if (hasValue(remoteData) && remoteData.isStale) {
|
|
||||||
requestFn();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> {
|
export abstract class DataService<T extends CacheableObject> implements UpdateDataService<T> {
|
||||||
protected abstract requestService: RequestService;
|
protected abstract requestService: RequestService;
|
||||||
protected abstract rdbService: RemoteDataBuildService;
|
protected abstract rdbService: RemoteDataBuildService;
|
||||||
@@ -332,6 +310,30 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
return this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An operator that will call the given function if the incoming RemoteData is stale and
|
||||||
|
* shouldReRequest is true
|
||||||
|
*
|
||||||
|
* @param shouldReRequest Whether or not to call the re-request function if the RemoteData is stale
|
||||||
|
* @param requestFn The function to call if the RemoteData is stale and shouldReRequest is
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
protected reRequestStaleRemoteData<O>(shouldReRequest: boolean, requestFn: () => Observable<RemoteData<O>>) {
|
||||||
|
return (source: Observable<RemoteData<O>>): Observable<RemoteData<O>> => {
|
||||||
|
if (shouldReRequest === true) {
|
||||||
|
return source.pipe(
|
||||||
|
tap((remoteData: RemoteData<O>) => {
|
||||||
|
if (hasValue(remoteData) && remoteData.isStale) {
|
||||||
|
requestFn();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
|
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
|
||||||
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
|
||||||
@@ -358,7 +360,12 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
return this.rdbService.buildSingle<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
|
// cached completed object
|
||||||
|
skipWhile((rd: RemoteData<T>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
|
||||||
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
this.findByHref(href$, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -390,7 +397,12 @@ export abstract class DataService<T extends CacheableObject> implements UpdateDa
|
|||||||
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
this.createAndSendGetRequest(requestHref$, useCachedVersionIfAvailable);
|
||||||
|
|
||||||
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
return this.rdbService.buildList<T>(requestHref$, ...linksToFollow).pipe(
|
||||||
reRequestStaleRemoteData(reRequestOnStale, () =>
|
// This skip ensures that if a stale object is present in the cache when you do a
|
||||||
|
// call it isn't immediately returned, but we wait until the remote data for the new request
|
||||||
|
// is created. If useCachedVersionIfAvailable is false it also ensures you don't get a
|
||||||
|
// cached completed object
|
||||||
|
skipWhile((rd: RemoteData<PaginatedList<T>>) => useCachedVersionIfAvailable ? rd.isStale : rd.hasCompleted),
|
||||||
|
this.reRequestStaleRemoteData(reRequestOnStale, () =>
|
||||||
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
this.findAllByHref(href$, findListOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -32,6 +32,8 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
let resolver: Resolve<RemoteData<any>>;
|
let resolver: Resolve<RemoteData<any>>;
|
||||||
let object: DSpaceObject;
|
let object: DSpaceObject;
|
||||||
|
let route;
|
||||||
|
let parentRoute;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
object = {
|
object = {
|
||||||
@@ -50,6 +52,16 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
authService = jasmine.createSpyObj('authService', {
|
authService = jasmine.createSpyObj('authService', {
|
||||||
isAuthenticated: observableOf(true)
|
isAuthenticated: observableOf(true)
|
||||||
});
|
});
|
||||||
|
parentRoute = {
|
||||||
|
params: {
|
||||||
|
id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
route = {
|
||||||
|
params: {
|
||||||
|
},
|
||||||
|
parent: parentRoute
|
||||||
|
};
|
||||||
guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,10 +71,17 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
|
|
||||||
describe('getObjectUrl', () => {
|
describe('getObjectUrl', () => {
|
||||||
it('should return the resolved object\'s selflink', (done) => {
|
it('should return the resolved object\'s selflink', (done) => {
|
||||||
guard.getObjectUrl(undefined, undefined).subscribe((selflink) => {
|
guard.getObjectUrl(route, undefined).subscribe((selflink) => {
|
||||||
expect(selflink).toEqual(object.self);
|
expect(selflink).toEqual(object.self);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getRouteWithDSOId', () => {
|
||||||
|
it('should return the route that has the UUID of the DSO', () => {
|
||||||
|
const foundRoute = (guard as any).getRouteWithDSOId(route);
|
||||||
|
expect(foundRoute).toBe(parentRoute);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -7,6 +7,7 @@ import { map } from 'rxjs/operators';
|
|||||||
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
||||||
import { FeatureAuthorizationGuard } from './feature-authorization.guard';
|
import { FeatureAuthorizationGuard } from './feature-authorization.guard';
|
||||||
import { AuthService } from '../../../auth/auth.service';
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
import { hasNoValue, hasValue } from '../../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
|
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
|
||||||
@@ -24,9 +25,22 @@ export abstract class DsoPageFeatureGuard<T extends DSpaceObject> extends Featur
|
|||||||
* Check authorization rights for the object resolved using the provided resolver
|
* Check authorization rights for the object resolved using the provided resolver
|
||||||
*/
|
*/
|
||||||
getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
||||||
return (this.resolver.resolve(route, state) as Observable<RemoteData<T>>).pipe(
|
const routeWithObjectID = this.getRouteWithDSOId(route);
|
||||||
|
return (this.resolver.resolve(routeWithObjectID, state) as Observable<RemoteData<T>>).pipe(
|
||||||
getAllSucceededRemoteDataPayload(),
|
getAllSucceededRemoteDataPayload(),
|
||||||
map((dso) => dso.self)
|
map((dso) => dso.self)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to resolve resolve (parent) route that contains the UUID of the DSO
|
||||||
|
* @param route The current route
|
||||||
|
*/
|
||||||
|
protected getRouteWithDSOId(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot {
|
||||||
|
let routeWithDSOId = route;
|
||||||
|
while (hasNoValue(routeWithDSOId.params.id) && hasValue(routeWithDSOId.parent)) {
|
||||||
|
routeWithDSOId = routeWithDSOId.parent;
|
||||||
|
}
|
||||||
|
return routeWithDSOId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,7 @@ import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|||||||
import { NoContent } from '../shared/NoContent.model';
|
import { NoContent } from '../shared/NoContent.model';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
|
import { getFirstCompletedRemoteData } from '../shared/operators';
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
/**
|
/**
|
||||||
@@ -57,15 +58,23 @@ class DataServiceImpl extends ItemDataService {
|
|||||||
super(requestService, rdbService, store, bs, objectCache, halService, notificationsService, http, comparator, bundleService);
|
super(requestService, rdbService, store, bs, objectCache, halService, notificationsService, http, comparator, bundleService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint based on a collection
|
||||||
|
* @param collectionID The ID of the collection to base the endpoint on
|
||||||
|
*/
|
||||||
|
public getCollectionEndpoint(collectionID: string): Observable<string> {
|
||||||
|
return this.collectionService.getIDHrefObs(collectionID).pipe(
|
||||||
|
switchMap((href: string) => this.halService.getEndpoint(this.collectionLinkPath, href))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the endpoint to be based on a collection
|
* Set the endpoint to be based on a collection
|
||||||
* @param collectionID The ID of the collection to base the endpoint on
|
* @param collectionID The ID of the collection to base the endpoint on
|
||||||
*/
|
*/
|
||||||
private setCollectionEndpoint(collectionID: string) {
|
private setCollectionEndpoint(collectionID: string) {
|
||||||
this.collectionEndpoint = true;
|
this.collectionEndpoint = true;
|
||||||
this.endpoint$ = this.collectionService.getIDHrefObs(collectionID).pipe(
|
this.endpoint$ = this.getCollectionEndpoint(collectionID);
|
||||||
switchMap((href: string) => this.halService.getEndpoint(this.collectionLinkPath, href))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,6 +139,7 @@ class DataServiceImpl extends ItemDataService {
|
|||||||
deleteByCollectionID(item: Item, collectionID: string): Observable<boolean> {
|
deleteByCollectionID(item: Item, collectionID: string): Observable<boolean> {
|
||||||
this.setRegularEndpoint();
|
this.setRegularEndpoint();
|
||||||
return super.delete(item.uuid).pipe(
|
return super.delete(item.uuid).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
map((response: RemoteData<NoContent>) => hasValue(response) && response.hasSucceeded)
|
map((response: RemoteData<NoContent>) => hasValue(response) && response.hasSucceeded)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -209,5 +219,13 @@ export class ItemTemplateDataService implements UpdateDataService<Item> {
|
|||||||
deleteByCollectionID(item: Item, collectionID: string): Observable<boolean> {
|
deleteByCollectionID(item: Item, collectionID: string): Observable<boolean> {
|
||||||
return this.dataService.deleteByCollectionID(item, collectionID);
|
return this.dataService.deleteByCollectionID(item, collectionID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the endpoint based on a collection
|
||||||
|
* @param collectionID The ID of the collection to base the endpoint on
|
||||||
|
*/
|
||||||
|
getCollectionEndpoint(collectionID: string): Observable<string> {
|
||||||
|
return this.dataService.getCollectionEndpoint(collectionID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
@@ -110,7 +110,6 @@ export abstract class TasksService<T extends CacheableObject> extends DataServic
|
|||||||
find((href: string) => hasValue(href)),
|
find((href: string) => hasValue(href)),
|
||||||
mergeMap((href) => this.findByHref(href, false, true).pipe(
|
mergeMap((href) => this.findByHref(href, false, true).pipe(
|
||||||
getAllCompletedRemoteData(),
|
getAllCompletedRemoteData(),
|
||||||
filter((rd: RemoteData<T>) => !rd.isSuccessStale),
|
|
||||||
tap(() => this.requestService.setStaleByHrefSubstring(href)))
|
tap(() => this.requestService.setStaleByHrefSubstring(href)))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
<span *ngIf="dso.allMetadata(['publicationvolume.volumeNumber']).length > 0"
|
<span *ngIf="dso.allMetadata(['publicationvolume.volumeNumber']).length > 0"
|
||||||
|
@@ -7,6 +7,8 @@ import { JournalIssueSearchResultListElementComponent } from './journal-issue-se
|
|||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent;
|
let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<JournalIssueSearchResultListElementComponent>;
|
let fixture: ComponentFixture<JournalIssueSearchResultListElementComponent>;
|
||||||
@@ -60,7 +62,8 @@ describe('JournalIssueSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe],
|
declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
<span *ngIf="dso.allMetadata(['journal.title']).length > 0"
|
<span *ngIf="dso.allMetadata(['journal.title']).length > 0"
|
||||||
|
@@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { JournalVolumeSearchResultListElementComponent } from './journal-volume-search-result-list-element.component';
|
import { JournalVolumeSearchResultListElementComponent } from './journal-volume-search-result-list-element.component';
|
||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent;
|
let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<JournalVolumeSearchResultListElementComponent>;
|
let fixture: ComponentFixture<JournalVolumeSearchResultListElementComponent>;
|
||||||
@@ -59,7 +61,8 @@ describe('JournalVolumeSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe],
|
declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
<span *ngIf="dso.allMetadata(['creativeworkseries.issn']).length > 0"
|
<span *ngIf="dso.allMetadata(['creativeworkseries.issn']).length > 0"
|
||||||
|
@@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let journalListElementComponent: JournalSearchResultListElementComponent;
|
let journalListElementComponent: JournalSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<JournalSearchResultListElementComponent>;
|
let fixture: ComponentFixture<JournalSearchResultListElementComponent>;
|
||||||
@@ -55,7 +57,8 @@ describe('JournalSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [JournalSearchResultListElementComponent, TruncatePipe],
|
declarations: [JournalSearchResultListElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -7,6 +7,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
|||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent;
|
let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<OrgUnitSearchResultListElementComponent>;
|
let fixture: ComponentFixture<OrgUnitSearchResultListElementComponent>;
|
||||||
@@ -53,7 +55,8 @@ describe('OrgUnitSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe],
|
declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [ NO_ERRORS_SCHEMA ]
|
schemas: [ NO_ERRORS_SCHEMA ]
|
||||||
|
@@ -7,6 +7,8 @@ import { PersonSearchResultListElementComponent } from './person-search-result-l
|
|||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let personListElementComponent: PersonSearchResultListElementComponent;
|
let personListElementComponent: PersonSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<PersonSearchResultListElementComponent>;
|
let fixture: ComponentFixture<PersonSearchResultListElementComponent>;
|
||||||
@@ -53,7 +55,8 @@ describe('PersonSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [PersonSearchResultListElementComponent, TruncatePipe],
|
declarations: [PersonSearchResultListElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead"
|
class="lead item-list-title"
|
||||||
[innerHTML]="firstMetadataValue('dc.title')"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<!--<span class="text-muted">-->
|
<!--<span class="text-muted">-->
|
||||||
<!--<ds-truncatable-part [id]="dso.id" [minLines]="1">-->
|
<!--<ds-truncatable-part [id]="dso.id" [minLines]="1">-->
|
||||||
<!--<span *ngIf="dso.allMetadata(['project.identifier.status']).length > 0"-->
|
<!--<span *ngIf="dso.allMetadata(['project.identifier.status']).length > 0"-->
|
||||||
|
@@ -6,6 +6,8 @@ import { ProjectSearchResultListElementComponent } from './project-search-result
|
|||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let projectListElementComponent: ProjectSearchResultListElementComponent;
|
let projectListElementComponent: ProjectSearchResultListElementComponent;
|
||||||
let fixture: ComponentFixture<ProjectSearchResultListElementComponent>;
|
let fixture: ComponentFixture<ProjectSearchResultListElementComponent>;
|
||||||
@@ -53,7 +55,8 @@ describe('ProjectSearchResultListElementComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ProjectSearchResultListElementComponent, TruncatePipe],
|
declarations: [ProjectSearchResultListElementComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: {} }
|
{ provide: TruncatableService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -9,6 +9,7 @@ import { isNotEmpty } from '../../../../../shared/empty.util';
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModal)
|
||||||
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.SideBarSearchModalCurrent)
|
||||||
@@ -23,8 +24,10 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
export class PersonSidebarSearchListElementComponent extends SidebarSearchListElementComponent<ItemSearchResult, Item> {
|
||||||
constructor(protected truncatableService: TruncatableService,
|
constructor(protected truncatableService: TruncatableService,
|
||||||
protected linkService: LinkService,
|
protected linkService: LinkService,
|
||||||
protected translateService: TranslateService) {
|
protected translateService: TranslateService,
|
||||||
super(truncatableService, linkService);
|
protected dsoNameService: DSONameService
|
||||||
|
) {
|
||||||
|
super(truncatableService, linkService, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -27,6 +27,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
|
||||||
import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component';
|
import { OrgUnitSearchResultListSubmissionElementComponent } from './org-unit-search-result-list-submission-element.component';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
|
||||||
let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent;
|
let personListElementComponent: OrgUnitSearchResultListSubmissionElementComponent;
|
||||||
let fixture: ComponentFixture<OrgUnitSearchResultListSubmissionElementComponent>;
|
let fixture: ComponentFixture<OrgUnitSearchResultListSubmissionElementComponent>;
|
||||||
@@ -115,6 +117,7 @@ describe('OrgUnitSearchResultListSubmissionElementComponent', () => {
|
|||||||
{ provide: DSOChangeAnalyzer, useValue: {} },
|
{ provide: DSOChangeAnalyzer, useValue: {} },
|
||||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
@@ -19,6 +19,7 @@ import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
|||||||
import { ItemDataService } from '../../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../../core/data/item-data.service';
|
||||||
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
|
import { NameVariantModalComponent } from '../../name-variant-modal/name-variant-modal.component';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal)
|
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModal)
|
||||||
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
|
@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
|
||||||
@@ -44,8 +45,10 @@ export class OrgUnitSearchResultListSubmissionElementComponent extends SearchRes
|
|||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private itemDataService: ItemDataService,
|
private itemDataService: ItemDataService,
|
||||||
private bitstreamDataService: BitstreamDataService,
|
private bitstreamDataService: BitstreamDataService,
|
||||||
private selectableListService: SelectableListService) {
|
private selectableListService: SelectableListService,
|
||||||
super(truncatableService);
|
protected dsoNameService: DSONameService
|
||||||
|
) {
|
||||||
|
super(truncatableService, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
@@ -19,6 +19,7 @@ import { NameVariantModalComponent } from '../../name-variant-modal/name-variant
|
|||||||
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
import { MetadataValue } from '../../../../../core/shared/metadata.models';
|
||||||
import { ItemDataService } from '../../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../../core/data/item-data.service';
|
||||||
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
|
@listableObjectComponent('PersonSearchResult', ViewMode.ListElement, Context.EntitySearchModalWithNameVariants)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -42,8 +43,10 @@ export class PersonSearchResultListSubmissionElementComponent extends SearchResu
|
|||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private itemDataService: ItemDataService,
|
private itemDataService: ItemDataService,
|
||||||
private bitstreamDataService: BitstreamDataService,
|
private bitstreamDataService: BitstreamDataService,
|
||||||
private selectableListService: SelectableListService) {
|
private selectableListService: SelectableListService,
|
||||||
super(truncatableService);
|
protected dsoNameService: DSONameService
|
||||||
|
) {
|
||||||
|
super(truncatableService, dsoNameService);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
25
src/app/footer/themed-footer.component.ts
Normal file
25
src/app/footer/themed-footer.component.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { FooterComponent } from './footer.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for FooterComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-footer',
|
||||||
|
styleUrls: ['footer.component.scss'],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedFooterComponent extends ThemedComponent<FooterComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'FooterComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/footer/footer.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./footer.component`);
|
||||||
|
}
|
||||||
|
}
|
26
src/app/forbidden/themed-forbidden.component.ts
Normal file
26
src/app/forbidden/themed-forbidden.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||||
|
import { ForbiddenComponent } from './forbidden.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for ForbiddenComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-forbidden',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
export class ThemedForbiddenComponent extends ThemedComponent<ForbiddenComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'ForbiddenComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../themes/${themeName}/app/forbidden/forbidden.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./forbidden.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-forgot-email',
|
selector: 'ds-forgot-email',
|
||||||
|
styleUrls: ['./forgot-email.component.scss'],
|
||||||
templateUrl: './forgot-email.component.html'
|
templateUrl: './forgot-email.component.html'
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { ForgotEmailComponent } from './forgot-email.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for ForgotEmailComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-forgot-email',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: './../../shared/theme-support/themed.component.html'
|
||||||
|
})
|
||||||
|
export class ThemedForgotEmailComponent extends ThemedComponent<ForgotEmailComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'ForgotEmailComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/forgot-password/forgot-password-email/forgot-email.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./forgot-email.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user