mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'origin/main' into CST-3782-fix-repeatable-fields
This commit is contained in:
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -80,6 +80,19 @@ jobs:
|
||||
docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
|
||||
docker container ls
|
||||
|
||||
# Wait until the REST API returns a 200 response (or for a max of 30 seconds)
|
||||
# https://github.com/nev7n/wait_for_response
|
||||
- name: Wait for DSpace REST Backend to be ready (for e2e tests)
|
||||
uses: nev7n/wait_for_response@v1
|
||||
with:
|
||||
# We use the 'sites' endpoint to also ensure the database is ready
|
||||
url: 'http://localhost:8080/server/api/core/sites'
|
||||
responseCode: 200
|
||||
timeout: 30000
|
||||
|
||||
- name: Get DSpace REST Backend info/properties
|
||||
run: curl http://localhost:8080/server/api
|
||||
|
||||
- name: Run e2e tests (integration tests)
|
||||
run: yarn run e2e:ci
|
||||
|
||||
|
@@ -10,7 +10,7 @@ const targetPath = './src/environments/environment.ts';
|
||||
const colors = require('colors');
|
||||
require('dotenv').config();
|
||||
const merge = require('deepmerge');
|
||||
|
||||
const mergeOptions = { arrayMerge: (destinationArray, sourceArray, options) => sourceArray };
|
||||
const environment = process.argv[2];
|
||||
let environmentFilePath;
|
||||
let production = false;
|
||||
@@ -45,10 +45,10 @@ const processEnv = {
|
||||
} as GlobalConfig;
|
||||
|
||||
import(environmentFilePath)
|
||||
.then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv])))
|
||||
.then((file) => generateEnvironmentFile(merge.all([commonEnv, file.environment, processEnv], mergeOptions)))
|
||||
.catch(() => {
|
||||
console.log(colors.yellow.bold(`No specific environment file found for ` + environment));
|
||||
generateEnvironmentFile(merge(commonEnv, processEnv))
|
||||
generateEnvironmentFile(merge(commonEnv, processEnv, mergeOptions))
|
||||
});
|
||||
|
||||
function generateEnvironmentFile(file: GlobalConfig): void {
|
||||
@@ -65,7 +65,7 @@ function generateEnvironmentFile(file: GlobalConfig): void {
|
||||
}
|
||||
|
||||
// allow to override a few important options by environment variables
|
||||
function createServerConfig(host?: string, port?: string, nameSpace?: string, ssl?: string): ServerConfig {
|
||||
function createServerConfig(host?: string, port?: string, nameSpace?: string, ssl?: string): ServerConfig {
|
||||
const result = {} as any;
|
||||
if (hasValue(host)) {
|
||||
result.host = host;
|
||||
|
@@ -7,10 +7,11 @@ import {
|
||||
of as observableOf,
|
||||
Subscription,
|
||||
BehaviorSubject,
|
||||
combineLatest as observableCombineLatest, ObservedValueOf,
|
||||
combineLatest as observableCombineLatest,
|
||||
ObservedValueOf,
|
||||
} from 'rxjs';
|
||||
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
|
||||
import {buildPaginatedList, PaginatedList} from '../../../../../core/data/paginated-list.model';
|
||||
import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||
import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
|
||||
import { GroupDataService } from '../../../../../core/eperson/group-data.service';
|
||||
@@ -19,11 +20,12 @@ import { Group } from '../../../../../core/eperson/models/group.model';
|
||||
import {
|
||||
getRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstCompletedRemoteData, getAllCompletedRemoteData
|
||||
getFirstCompletedRemoteData,
|
||||
getAllCompletedRemoteData
|
||||
} from '../../../../../core/shared/operators';
|
||||
import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
|
||||
import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
|
||||
import {EpersonDtoModel} from '../../../../../core/eperson/models/eperson-dto.model';
|
||||
import { EpersonDtoModel } from '../../../../../core/eperson/models/eperson-dto.model';
|
||||
|
||||
/**
|
||||
* Keys to keep track of specific subscriptions
|
||||
|
@@ -11,6 +11,8 @@ import { Collection } from '../../../../../core/shared/collection.model';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
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', () => {
|
||||
let component: CollectionAdminSearchResultListElementComponent;
|
||||
@@ -33,7 +35,8 @@ describe('CollectionAdminSearchResultListElementComponent', () => {
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
declarations: [CollectionAdminSearchResultListElementComponent],
|
||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
||||
providers: [{ provide: TruncatableService, useValue: {} },
|
||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -11,6 +11,8 @@ import { CommunityAdminSearchResultListElementComponent } from './community-admi
|
||||
import { CommunitySearchResult } from '../../../../../shared/object-collection/shared/community-search-result.model';
|
||||
import { Community } from '../../../../../core/shared/community.model';
|
||||
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', () => {
|
||||
let component: CommunityAdminSearchResultListElementComponent;
|
||||
@@ -33,7 +35,8 @@ describe('CommunityAdminSearchResultListElementComponent', () => {
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
declarations: [CommunityAdminSearchResultListElementComponent],
|
||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
||||
providers: [{ provide: TruncatableService, useValue: {} },
|
||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -8,6 +8,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { ItemAdminSearchResultListElementComponent } from './item-admin-search-result-list-element.component';
|
||||
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', () => {
|
||||
let component: ItemAdminSearchResultListElementComponent;
|
||||
@@ -30,7 +32,8 @@ describe('ItemAdminSearchResultListElementComponent', () => {
|
||||
RouterTestingModule.withRoutes([])
|
||||
],
|
||||
declarations: [ItemAdminSearchResultListElementComponent],
|
||||
providers: [{ provide: TruncatableService, useValue: {} }],
|
||||
providers: [{ provide: TruncatableService, useValue: {} },
|
||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
|
@@ -56,19 +56,19 @@ describe('ItemAdminSearchResultActionsComponent', () => {
|
||||
it('should render an edit button with the correct link', () => {
|
||||
const button = fixture.debugElement.query(By.css('a.edit-link'));
|
||||
const link = button.nativeElement.href;
|
||||
expect(link).toContain(getItemEditRoute(id));
|
||||
expect(link).toContain(getItemEditRoute(item));
|
||||
});
|
||||
|
||||
it('should render a delete button with the correct link', () => {
|
||||
const button = fixture.debugElement.query(By.css('a.delete-link'));
|
||||
const link = button.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_DELETE_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_DELETE_PATH).toString());
|
||||
});
|
||||
|
||||
it('should render a move button with the correct link', () => {
|
||||
const a = fixture.debugElement.query(By.css('a.move-link'));
|
||||
const link = a.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_MOVE_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_MOVE_PATH).toString());
|
||||
});
|
||||
|
||||
describe('when the item is not withdrawn', () => {
|
||||
@@ -80,7 +80,7 @@ describe('ItemAdminSearchResultActionsComponent', () => {
|
||||
it('should render a withdraw button with the correct link', () => {
|
||||
const a = fixture.debugElement.query(By.css('a.withdraw-link'));
|
||||
const link = a.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_WITHDRAW_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_WITHDRAW_PATH).toString());
|
||||
});
|
||||
|
||||
it('should not render a reinstate button with the correct link', () => {
|
||||
@@ -103,7 +103,7 @@ describe('ItemAdminSearchResultActionsComponent', () => {
|
||||
it('should render a reinstate button with the correct link', () => {
|
||||
const a = fixture.debugElement.query(By.css('a.reinstate-link'));
|
||||
const link = a.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_REINSTATE_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_REINSTATE_PATH).toString());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,7 +116,7 @@ describe('ItemAdminSearchResultActionsComponent', () => {
|
||||
it('should render a make private button with the correct link', () => {
|
||||
const a = fixture.debugElement.query(By.css('a.private-link'));
|
||||
const link = a.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_PRIVATE_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_PRIVATE_PATH).toString());
|
||||
});
|
||||
|
||||
it('should not render a make public button with the correct link', () => {
|
||||
@@ -139,7 +139,7 @@ describe('ItemAdminSearchResultActionsComponent', () => {
|
||||
it('should render a make private button with the correct link', () => {
|
||||
const a = fixture.debugElement.query(By.css('a.public-link'));
|
||||
const link = a.nativeElement.href;
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(id), ITEM_EDIT_PUBLIC_PATH).toString());
|
||||
expect(link).toContain(new URLCombiner(getItemEditRoute(item), ITEM_EDIT_PUBLIC_PATH).toString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -34,7 +34,7 @@ export class ItemAdminSearchResultActionsComponent {
|
||||
* Returns the path to the edit page of this item
|
||||
*/
|
||||
getEditRoute(): string {
|
||||
return getItemEditRoute(this.item.uuid);
|
||||
return getItemEditRoute(this.item);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -16,6 +16,8 @@ import { Item } from '../../../../../core/shared/item.model';
|
||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||
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', () => {
|
||||
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
|
||||
@@ -49,6 +51,7 @@ describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: LinkService, useValue: linkService },
|
||||
{ provide: DSONameService, useClass: DSONameServiceMock }
|
||||
],
|
||||
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 { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
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)
|
||||
@Component({
|
||||
@@ -29,8 +30,11 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S
|
||||
*/
|
||||
public item$: Observable<Item>;
|
||||
|
||||
constructor(private linkService: LinkService, protected truncatableService: TruncatableService) {
|
||||
super(truncatableService);
|
||||
constructor(private linkService: LinkService,
|
||||
protected truncatableService: TruncatableService,
|
||||
protected dsoNameService: DSONameService
|
||||
) {
|
||||
super(truncatableService, dsoNameService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -18,10 +18,14 @@ import { hasValue } from '../../shared/empty.util';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||
import {
|
||||
createSuccessfulRemoteDataObject,
|
||||
createSuccessfulRemoteDataObject$
|
||||
} from '../../shared/remote-data.utils';
|
||||
import { RouterStub } from '../../shared/testing/router.stub';
|
||||
import { getItemEditRoute } from '../../+item-page/item-page-routing-paths';
|
||||
import { getEntityEditRoute, getItemEditRoute } from '../../+item-page/item-page-routing-paths';
|
||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
|
||||
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
|
||||
@@ -109,9 +113,9 @@ describe('EditBitstreamPageComponent', () => {
|
||||
self: 'bitstream-selflink'
|
||||
},
|
||||
bundle: createSuccessfulRemoteDataObject$({
|
||||
item: createSuccessfulRemoteDataObject$({
|
||||
item: createSuccessfulRemoteDataObject$(Object.assign(new Item(), {
|
||||
uuid: 'some-uuid'
|
||||
})
|
||||
}))
|
||||
})
|
||||
});
|
||||
bitstreamService = jasmine.createSpyObj('bitstreamService', {
|
||||
@@ -237,14 +241,14 @@ describe('EditBitstreamPageComponent', () => {
|
||||
it('should redirect to the item edit page on the bitstreams tab with the itemId from the component', () => {
|
||||
comp.itemId = 'some-uuid1';
|
||||
comp.navigateToItemEditBitstreams();
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute('some-uuid1'), 'bitstreams']);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid1'), 'bitstreams']);
|
||||
});
|
||||
});
|
||||
describe('when navigateToItemEditBitstreams is called, and the component does not have an itemId', () => {
|
||||
it('should redirect to the item edit page on the bitstreams tab with the itemId from the bundle links ', () => {
|
||||
comp.itemId = undefined;
|
||||
comp.navigateToItemEditBitstreams();
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute('some-uuid'), 'bitstreams']);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getEntityEditRoute(null, 'some-uuid'), 'bitstreams']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -33,9 +33,8 @@ import { Metadata } from '../../core/shared/metadata.utils';
|
||||
import { Location } from '@angular/common';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||
import { getItemEditRoute } from '../../+item-page/item-page-routing-paths';
|
||||
import { getEntityEditRoute, getItemEditRoute } from '../../+item-page/item-page-routing-paths';
|
||||
import { Bundle } from '../../core/shared/bundle.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-bitstream-page',
|
||||
@@ -264,9 +263,17 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The ID of the item the bitstream originates from
|
||||
* Taken from the current query parameters when present
|
||||
* This will determine the route of the item edit page to return to
|
||||
*/
|
||||
itemId: string;
|
||||
|
||||
/**
|
||||
* The entity type of the item the bitstream originates from
|
||||
* Taken from the current query parameters when present
|
||||
* This will determine the route of the item edit page to return to
|
||||
*/
|
||||
entityType: string;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
@@ -293,6 +300,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
this.formGroup = this.formService.createFormGroup(this.formModel);
|
||||
|
||||
this.itemId = this.route.snapshot.queryParams.itemId;
|
||||
this.entityType = this.route.snapshot.queryParams.entityType;
|
||||
this.bitstreamRD$ = this.route.data.pipe(map((data) => data.bitstream));
|
||||
this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
|
||||
|
||||
@@ -499,10 +507,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
navigateToItemEditBitstreams() {
|
||||
if (hasValue(this.itemId)) {
|
||||
this.router.navigate([getItemEditRoute(this.itemId), 'bitstreams']);
|
||||
this.router.navigate([getEntityEditRoute(this.entityType, this.itemId), 'bitstreams']);
|
||||
} else {
|
||||
this.bitstream.bundle.pipe(getFirstSucceededRemoteDataPayload(),
|
||||
mergeMap((bundle: Bundle) => bundle.item.pipe(getFirstSucceededRemoteDataPayload(), map((item: Item) => item.uuid))))
|
||||
mergeMap((bundle: Bundle) => bundle.item.pipe(getFirstSucceededRemoteDataPayload())))
|
||||
.subscribe((item) => {
|
||||
this.router.navigate(([getItemEditRoute(item), 'bitstreams']));
|
||||
});
|
||||
|
@@ -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 { NgModule } from '@angular/core';
|
||||
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 { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||
import { ThemedBrowseBySwitcherComponent } from './+browse-by-switcher/themed-browse-by-switcher.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -14,7 +14,7 @@ import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.reso
|
||||
children: [
|
||||
{
|
||||
path: ':id',
|
||||
component: BrowseBySwitcherComponent,
|
||||
component: ThemedBrowseBySwitcherComponent,
|
||||
canActivate: [BrowseByGuard],
|
||||
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
||||
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 { BrowseByDatePageComponent } from './+browse-by-date-page/browse-by-date-page.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 = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -20,6 +21,7 @@ const ENTRY_COMPONENTS = [
|
||||
],
|
||||
declarations: [
|
||||
BrowseBySwitcherComponent,
|
||||
ThemedBrowseBySwitcherComponent,
|
||||
...ENTRY_COMPONENTS
|
||||
],
|
||||
exports: [
|
||||
|
@@ -24,6 +24,10 @@ export function getCollectionEditRolesRoute(id) {
|
||||
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_EDIT_PATH = 'edit';
|
||||
export const COLLECTION_EDIT_ROLES_PATH = 'roles';
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { CollectionPageComponent } from './collection-page.component';
|
||||
import { CollectionPageResolver } from './collection-page.resolver';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -62,7 +62,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: CollectionPageComponent,
|
||||
component: ThemedCollectionPageComponent,
|
||||
pathMatch: 'full',
|
||||
}
|
||||
],
|
||||
|
@@ -35,7 +35,7 @@
|
||||
</ds-comcol-page-content>
|
||||
</header>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'collections'" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="collectionPageRoute$ | async" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="comcol-page-browse-section">
|
||||
|
@@ -15,13 +15,19 @@ import { Bitstream } from '../core/shared/bitstream.model';
|
||||
import { Collection } from '../core/shared/collection.model';
|
||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteData, redirectOn4xx, toDSpaceObjectListRD } from '../core/shared/operators';
|
||||
import {
|
||||
getAllSucceededRemoteDataPayload,
|
||||
getFirstSucceededRemoteData,
|
||||
redirectOn4xx,
|
||||
toDSpaceObjectListRD
|
||||
} from '../core/shared/operators';
|
||||
|
||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import {PaginationChangeEvent} from '../shared/pagination/paginationChangeEvent.interface';
|
||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-collection-page',
|
||||
@@ -44,6 +50,11 @@ export class CollectionPageComponent implements OnInit {
|
||||
sortConfig: SortOptions
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Route to the community page
|
||||
*/
|
||||
collectionPageRoute$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private collectionDataService: CollectionDataService,
|
||||
private searchService: SearchService,
|
||||
@@ -94,6 +105,11 @@ export class CollectionPageComponent implements OnInit {
|
||||
)
|
||||
);
|
||||
|
||||
this.collectionPageRoute$ = this.collectionRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((collection) => getCollectionPageRoute(collection.id))
|
||||
);
|
||||
|
||||
this.route.queryParams.pipe(take(1)).subscribe((params) => {
|
||||
this.metadata.processRemoteData(this.collectionRD$);
|
||||
});
|
||||
|
@@ -13,6 +13,7 @@ import { CollectionItemMapperComponent } from './collection-item-mapper/collecti
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -25,6 +26,7 @@ import { CollectionFormModule } from './collection-form/collection-form.module';
|
||||
],
|
||||
declarations: [
|
||||
CollectionPageComponent,
|
||||
ThemedCollectionPageComponent,
|
||||
CreateCollectionPageComponent,
|
||||
DeleteCollectionPageComponent,
|
||||
EditItemTemplatePageComponent,
|
||||
|
@@ -15,6 +15,7 @@ import { Collection } from '../../../core/shared/collection.model';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||
|
||||
describe('CollectionMetadataComponent', () => {
|
||||
let comp: CollectionMetadataComponent;
|
||||
@@ -35,11 +36,13 @@ describe('CollectionMetadataComponent', () => {
|
||||
self: { href: 'collection-selflink' }
|
||||
}
|
||||
});
|
||||
const collectionTemplateHref = 'rest/api/test/collections/template';
|
||||
|
||||
const itemTemplateServiceStub = Object.assign({
|
||||
findByCollectionID: () => createSuccessfulRemoteDataObject$(template),
|
||||
create: () => createSuccessfulRemoteDataObject$(template),
|
||||
deleteByCollectionID: () => observableOf(true)
|
||||
const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
|
||||
findByCollectionID: createSuccessfulRemoteDataObject$(template),
|
||||
create: createSuccessfulRemoteDataObject$(template),
|
||||
deleteByCollectionID: observableOf(true),
|
||||
getCollectionEndpoint: observableOf(collectionTemplateHref),
|
||||
});
|
||||
|
||||
const notificationsService = jasmine.createSpyObj('notificationsService', {
|
||||
@@ -50,7 +53,7 @@ describe('CollectionMetadataComponent', () => {
|
||||
remove: {}
|
||||
});
|
||||
const requestService = jasmine.createSpyObj('requestService', {
|
||||
removeByHrefSubstring: {}
|
||||
setStaleByHrefSubstring: {}
|
||||
});
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
@@ -87,14 +90,14 @@ describe('CollectionMetadataComponent', () => {
|
||||
it('should navigate to the collection\'s itemtemplate page', () => {
|
||||
spyOn(router, 'navigate');
|
||||
comp.addItemTemplate();
|
||||
expect(router.navigate).toHaveBeenCalledWith(['collections', collection.uuid, 'itemtemplate']);
|
||||
expect(router.navigate).toHaveBeenCalledWith([getCollectionItemTemplateRoute(collection.uuid)]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteItemTemplate', () => {
|
||||
describe('when delete returns a success', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(true));
|
||||
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true));
|
||||
comp.deleteItemTemplate();
|
||||
});
|
||||
|
||||
@@ -103,14 +106,15 @@ describe('CollectionMetadataComponent', () => {
|
||||
});
|
||||
|
||||
it('should reset related object and request cache', () => {
|
||||
expect(objectCache.remove).toHaveBeenCalledWith(template.self);
|
||||
expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(collection.self);
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collectionTemplateHref);
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(template.self);
|
||||
expect(requestService.setStaleByHrefSubstring).toHaveBeenCalledWith(collection.self);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when delete returns a failure', () => {
|
||||
beforeEach(() => {
|
||||
spyOn(itemTemplateService, 'deleteByCollectionID').and.returnValue(observableOf(false));
|
||||
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(false));
|
||||
comp.deleteItemTemplate();
|
||||
});
|
||||
|
||||
|
@@ -7,12 +7,13 @@ import { ItemTemplateDataService } from '../../../core/data/item-template-data.s
|
||||
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { getRemoteDataPayload, getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { switchMap, take } from 'rxjs/operators';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
import { switchMap, tap } from 'rxjs/operators';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||
|
||||
/**
|
||||
* Component for editing a collection's metadata
|
||||
@@ -53,8 +54,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
||||
*/
|
||||
initTemplateItem() {
|
||||
this.itemTemplateRD$ = this.dsoRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid))
|
||||
);
|
||||
}
|
||||
@@ -64,19 +64,20 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
||||
*/
|
||||
addItemTemplate() {
|
||||
const collection$ = this.dsoRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
take(1)
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
);
|
||||
const template$ = collection$.pipe(
|
||||
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid)),
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
take(1)
|
||||
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
)),
|
||||
);
|
||||
const templateHref$ = collection$.pipe(
|
||||
switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
|
||||
);
|
||||
|
||||
combineLatestObservable(collection$, template$).subscribe(([collection, template]) => {
|
||||
this.router.navigate(['collections', collection.uuid, 'itemtemplate']);
|
||||
combineLatestObservable(collection$, template$, templateHref$).subscribe(([collection, template, templateHref]) => {
|
||||
this.requestService.setStaleByHrefSubstring(templateHref);
|
||||
this.router.navigate([getCollectionItemTemplateRoute(collection.uuid)]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,23 +86,30 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
||||
*/
|
||||
deleteItemTemplate() {
|
||||
const collection$ = this.dsoRD$.pipe(
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
take(1)
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
);
|
||||
const template$ = collection$.pipe(
|
||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
|
||||
getFirstSucceededRemoteData(),
|
||||
getRemoteDataPayload(),
|
||||
take(1)
|
||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid).pipe(
|
||||
getFirstSucceededRemoteDataPayload(),
|
||||
)),
|
||||
);
|
||||
const templateHref$ = collection$.pipe(
|
||||
switchMap((collection) => this.itemTemplateService.getCollectionEndpoint(collection.id)),
|
||||
);
|
||||
|
||||
combineLatestObservable(collection$, template$).pipe(
|
||||
switchMap(([collection, template]) => {
|
||||
const success$ = this.itemTemplateService.deleteByCollectionID(template, collection.uuid);
|
||||
this.objectCache.remove(template.self);
|
||||
this.requestService.removeByHrefSubstring(collection.self);
|
||||
return success$;
|
||||
combineLatestObservable(collection$, template$, templateHref$).pipe(
|
||||
switchMap(([collection, template, templateHref]) => {
|
||||
return this.itemTemplateService.deleteByCollectionID(template, collection.uuid).pipe(
|
||||
tap((success: boolean) => {
|
||||
if (success) {
|
||||
this.objectCache.remove(templateHref);
|
||||
this.objectCache.remove(template.self);
|
||||
this.requestService.setStaleByHrefSubstring(template.self);
|
||||
this.requestService.setStaleByHrefSubstring(templateHref);
|
||||
this.requestService.setStaleByHrefSubstring(collection.self);
|
||||
}
|
||||
})
|
||||
);
|
||||
})
|
||||
).subscribe((success: boolean) => {
|
||||
if (success) {
|
||||
|
@@ -1,9 +1,13 @@
|
||||
<div class="container" *ngVar="(collectionRD$ | async)?.payload as collection">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2 class="border-bottom">{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}</h2>
|
||||
<ds-item-metadata [updateService]="itemTemplateService"></ds-item-metadata>
|
||||
<button [routerLink]="getCollectionEditUrl(collection)" class="btn btn-outline-secondary">{{ 'collection.edit.template.cancel' | translate }}</button>
|
||||
<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>
|
||||
<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>
|
||||
</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>
|
||||
|
@@ -9,7 +9,7 @@ import { ActivatedRoute } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
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';
|
||||
|
||||
describe('EditItemTemplatePageComponent', () => {
|
||||
@@ -24,11 +24,14 @@ describe('EditItemTemplatePageComponent', () => {
|
||||
id: 'collection-id',
|
||||
name: 'Fake Collection'
|
||||
});
|
||||
itemTemplateService = jasmine.createSpyObj('itemTemplateService', {
|
||||
findByCollectionID: createSuccessfulRemoteDataObject$({})
|
||||
});
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
declarations: [EditItemTemplatePageComponent],
|
||||
providers: [
|
||||
{ provide: ItemTemplateDataService, useValue: {} },
|
||||
{ provide: ItemTemplateDataService, useValue: itemTemplateService },
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: createSuccessfulRemoteDataObject(collection) }) } } }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
@@ -38,7 +41,6 @@ describe('EditItemTemplatePageComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditItemTemplatePageComponent);
|
||||
comp = fixture.componentInstance;
|
||||
itemTemplateService = (comp as any).itemTemplateService;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@@ -3,9 +3,12 @@ import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
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 { 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({
|
||||
selector: 'ds-edit-item-template-page',
|
||||
@@ -21,12 +24,27 @@ export class EditItemTemplatePageComponent implements OnInit {
|
||||
*/
|
||||
collectionRD$: Observable<RemoteData<Collection>>;
|
||||
|
||||
/**
|
||||
* The template item
|
||||
*/
|
||||
itemRD$: Observable<RemoteData<Item>>;
|
||||
|
||||
/**
|
||||
* The AlertType enumeration
|
||||
* @type {AlertType}
|
||||
*/
|
||||
AlertTypeEnum = AlertType;
|
||||
|
||||
constructor(protected route: ActivatedRoute,
|
||||
public itemTemplateService: ItemTemplateDataService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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 { RouterModule } from '@angular/router';
|
||||
|
||||
import { CommunityPageComponent } from './community-page.component';
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||
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 { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -45,7 +45,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: CommunityPageComponent,
|
||||
component: ThemedCommunityPageComponent,
|
||||
pathMatch: 'full',
|
||||
}
|
||||
],
|
||||
|
@@ -21,7 +21,7 @@
|
||||
</ds-comcol-page-content>
|
||||
</header>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'communities'" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="communityPageRoute$ | async" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="comcol-page-browse-section">
|
||||
|
@@ -13,8 +13,9 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
||||
|
||||
import { fadeInOut } from '../shared/animations/fade';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { redirectOn4xx } from '../core/shared/operators';
|
||||
import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../core/shared/operators';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { getCommunityPageRoute } from './community-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page',
|
||||
@@ -36,6 +37,12 @@ export class CommunityPageComponent implements OnInit {
|
||||
* The logo of this community
|
||||
*/
|
||||
logoRD$: Observable<RemoteData<Bitstream>>;
|
||||
|
||||
/**
|
||||
* Route to the community page
|
||||
*/
|
||||
communityPageRoute$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private communityDataService: CommunityDataService,
|
||||
private metadata: MetadataService,
|
||||
@@ -55,6 +62,10 @@ export class CommunityPageComponent implements OnInit {
|
||||
map((rd: RemoteData<Community>) => rd.payload),
|
||||
filter((community: Community) => hasValue(community)),
|
||||
mergeMap((community: Community) => community.logo));
|
||||
this.communityPageRoute$ = this.communityRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((community) => getCommunityPageRoute(community.id))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -11,6 +11,14 @@ import { CreateCommunityPageComponent } from './create-community-page/create-com
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.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({
|
||||
imports: [
|
||||
@@ -21,11 +29,10 @@ import { CommunityFormModule } from './community-form/community-form.module';
|
||||
CommunityFormModule
|
||||
],
|
||||
declarations: [
|
||||
CommunityPageComponent,
|
||||
CommunityPageSubCollectionListComponent,
|
||||
CommunityPageSubCommunityListComponent,
|
||||
CreateCommunityPageComponent,
|
||||
DeleteCommunityPageComponent
|
||||
...DECLARATIONS
|
||||
],
|
||||
exports: [
|
||||
...DECLARATIONS
|
||||
]
|
||||
})
|
||||
|
||||
|
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 { RouterModule } from '@angular/router';
|
||||
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({
|
||||
imports: [
|
||||
@@ -9,7 +9,7 @@ import { SubmissionImportExternalComponent } from '../submission/import-external
|
||||
{
|
||||
canActivate: [ AuthenticatedGuard ],
|
||||
path: '',
|
||||
component: SubmissionImportExternalComponent,
|
||||
component: ThemedSubmissionImportExternalComponent,
|
||||
pathMatch: 'full',
|
||||
data: {
|
||||
title: 'submission.import-external.page.title'
|
||||
|
@@ -17,7 +17,7 @@ import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operato
|
||||
import { UploaderComponent } from '../../../shared/uploader/uploader.component';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
import { getBitstreamModuleRoute } from '../../../app-routing-paths';
|
||||
import { getItemEditRoute } from '../../item-page-routing-paths';
|
||||
import { getEntityEditRoute } from '../../item-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-upload-bitstream',
|
||||
@@ -37,6 +37,12 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
itemId: string;
|
||||
|
||||
/**
|
||||
* The entity type of the item
|
||||
* This is fetched from the current URL and will determine the item's page route
|
||||
*/
|
||||
entityType: string;
|
||||
|
||||
/**
|
||||
* The item to upload a bitstream to
|
||||
*/
|
||||
@@ -100,6 +106,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.itemId = this.route.snapshot.params.id;
|
||||
this.entityType = this.route.snapshot.params['entity-type'];
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso));
|
||||
this.bundlesRD$ = this.itemRD$.pipe(
|
||||
switchMap((itemRD: RemoteData<Item>) => itemRD.payload.bundles)
|
||||
@@ -167,7 +174,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
|
||||
// Bring over the item ID as a query parameter
|
||||
const queryParams = { itemId: this.itemId };
|
||||
const queryParams = { itemId: this.itemId, entityType: this.entityType };
|
||||
this.router.navigate([getBitstreamModuleRoute(), bitstream.id, 'edit'], { queryParams: queryParams });
|
||||
}
|
||||
|
||||
@@ -193,7 +200,7 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
* When cancel is clicked, navigate back to the item's edit bitstreams page
|
||||
*/
|
||||
onCancel() {
|
||||
this.router.navigate([getItemEditRoute(this.itemId), 'bitstreams']);
|
||||
this.router.navigate([getEntityEditRoute(this.entityType, this.itemId), 'bitstreams']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
||||
import {
|
||||
FieldUpdate,
|
||||
FieldUpdates
|
||||
@@ -14,6 +14,7 @@ import { first, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { getItemPageRoute } from '../../item-page-routing-paths';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../item-page.resolver';
|
||||
import { getAllSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
@@ -29,13 +30,18 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
/**
|
||||
* The item to display the edit page for
|
||||
*/
|
||||
item: Item;
|
||||
@Input() item: Item;
|
||||
/**
|
||||
* The current values and updates for all this item's fields
|
||||
* Should be initialized in the initializeUpdates method of the child component
|
||||
*/
|
||||
updates$: Observable<FieldUpdates>;
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute: string;
|
||||
|
||||
/**
|
||||
* A subscription that checks when the item is deleted in cache and reloads the item by sending a new request
|
||||
* This is used to update the item in cache after bitstreams are deleted
|
||||
@@ -57,21 +63,24 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
* Initialize common properties between item-update components
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.itemUpdateSubscription = observableCombineLatest([this.route.data, this.route.parent.data]).pipe(
|
||||
map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)),
|
||||
map((data: any) => data.dso),
|
||||
tap((rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
}),
|
||||
switchMap((rd: RemoteData<Item>) => {
|
||||
return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW);
|
||||
}),
|
||||
getAllSucceededRemoteData()
|
||||
).subscribe((rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
this.postItemInit();
|
||||
this.initializeUpdates();
|
||||
});
|
||||
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(
|
||||
map(([data, parentData]: [Data, Data]) => Object.assign({}, data, parentData)),
|
||||
map((data: any) => data.dso),
|
||||
tap((rd: RemoteData<Item>) => {
|
||||
this.item = rd.payload;
|
||||
}),
|
||||
switchMap((rd: RemoteData<Item>) => {
|
||||
return this.itemService.findByHref(rd.payload._links.self.href, true, true, ...ITEM_PAGE_LINKS_TO_FOLLOW);
|
||||
}),
|
||||
getAllSucceededRemoteData()
|
||||
).subscribe((rd: RemoteData<Item>) => {
|
||||
this.setItem(rd.payload);
|
||||
});
|
||||
}
|
||||
|
||||
this.discardTimeOut = environment.item.edit.undoTimeout;
|
||||
this.url = this.router.url;
|
||||
@@ -90,6 +99,13 @@ export class AbstractItemUpdateComponent extends AbstractTrackableComponent impl
|
||||
this.initializeUpdates();
|
||||
}
|
||||
|
||||
setItem(item: Item) {
|
||||
this.item = item;
|
||||
this.itemPageRoute = getItemPageRoute(this.item);
|
||||
this.postItemInit();
|
||||
this.initializeUpdates();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (hasValue(this.itemUpdateSubscription)) {
|
||||
this.itemUpdateSubscription.unsubscribe();
|
||||
|
@@ -5,11 +5,18 @@
|
||||
<div class="pt-2">
|
||||
<ul class="nav nav-tabs justify-content-start">
|
||||
<li *ngFor="let page of pages" class="nav-item">
|
||||
<a class="nav-link"
|
||||
[ngClass]="{'active' : page === currentPage}"
|
||||
[routerLink]="['./' + page]">
|
||||
{{'item.edit.tabs.' + page + '.head' | translate}}
|
||||
<a *ngIf="(page.enabled | async)"
|
||||
class="nav-link"
|
||||
[ngClass]="{'active' : page.page === currentPage}"
|
||||
[routerLink]="['./' + page.page]">
|
||||
{{'item.edit.tabs.' + page.page + '.head' | translate}}
|
||||
</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>
|
||||
</ul>
|
||||
<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 { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ChangeDetectionStrategy, Component, Injector, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, CanActivate, Route, Router } from '@angular/router';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
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 { isNotEmpty } from '../../shared/empty.util';
|
||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-item-page',
|
||||
@@ -35,9 +36,9 @@ export class EditItemPageComponent implements OnInit {
|
||||
/**
|
||||
* 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.currentPage = this.route.snapshot.firstChild.routeConfig.path;
|
||||
});
|
||||
@@ -45,8 +46,20 @@ export class EditItemPageComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.pages = this.route.routeConfig.children
|
||||
.map((child: any) => child.path)
|
||||
.filter((path: string) => isNotEmpty(path)); // ignore reroutes
|
||||
.filter((child: Route) => isNotEmpty(child.path))
|
||||
.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));
|
||||
}
|
||||
|
||||
@@ -55,6 +68,6 @@ export class EditItemPageComponent implements OnInit {
|
||||
* @param item The item for which the url is requested
|
||||
*/
|
||||
getItemPage(item: Item): string {
|
||||
return getItemPageRoute(item.id);
|
||||
return getItemPageRoute(item);
|
||||
}
|
||||
}
|
||||
|
@@ -22,15 +22,17 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
|
||||
import { I18nBreadcrumbsService } from '../../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import {
|
||||
ITEM_EDIT_AUTHORIZATIONS_PATH,
|
||||
ITEM_EDIT_MOVE_PATH,
|
||||
ITEM_EDIT_DELETE_PATH,
|
||||
ITEM_EDIT_PUBLIC_PATH,
|
||||
ITEM_EDIT_MOVE_PATH,
|
||||
ITEM_EDIT_PRIVATE_PATH,
|
||||
ITEM_EDIT_PUBLIC_PATH,
|
||||
ITEM_EDIT_REINSTATE_PATH,
|
||||
ITEM_EDIT_WITHDRAW_PATH
|
||||
} from './edit-item-page.routing-paths';
|
||||
import { ItemPageReinstateGuard } from './item-page-reinstate.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
|
||||
@@ -57,22 +59,26 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
||||
{
|
||||
path: 'status',
|
||||
component: ItemStatusComponent,
|
||||
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true }
|
||||
data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'bitstreams',
|
||||
component: ItemBitstreamsComponent,
|
||||
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true }
|
||||
data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'metadata',
|
||||
component: ItemMetadataComponent,
|
||||
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }
|
||||
data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageEditMetadataGuard]
|
||||
},
|
||||
{
|
||||
path: 'relationships',
|
||||
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
|
||||
{
|
||||
@@ -89,12 +95,14 @@ import { ItemPageWithdrawGuard } from './item-page-withdraw.guard';
|
||||
{
|
||||
path: 'versionhistory',
|
||||
component: ItemVersionHistoryComponent,
|
||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }
|
||||
data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true },
|
||||
canActivate: [ItemPageAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: 'mapper',
|
||||
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,
|
||||
ResourcePolicyTargetResolver,
|
||||
ItemPageReinstateGuard,
|
||||
ItemPageWithdrawGuard
|
||||
ItemPageWithdrawGuard,
|
||||
ItemPageAdministratorGuard,
|
||||
ItemPageEditMetadataGuard,
|
||||
]
|
||||
})
|
||||
export class EditItemPageRoutingModule {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<div class="item-bitstreams" *ngVar="(bundles$ | async) as bundles">
|
||||
<div class="button-row top d-flex mt-2">
|
||||
<button class="mr-auto btn btn-success"
|
||||
[routerLink]="['/items/', item.id, 'bitstreams', 'new']"><i
|
||||
[routerLink]="[itemPageRoute, 'bitstreams', 'new']"><i
|
||||
class="fas fa-upload"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.bitstreams.upload-button" | translate}}</span>
|
||||
</button>
|
||||
|
@@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="{{columnSizes.columns[3].buildClasses()}} text-center row-element">
|
||||
<div class="btn-group bundle-action-buttons">
|
||||
<button [routerLink]="['/items/', item.id, 'bitstreams', 'new']"
|
||||
<button [routerLink]="[itemPageRoute, 'bitstreams', 'new']"
|
||||
[queryParams]="{bundle: bundle.id}"
|
||||
class="btn btn-outline-success btn-sm"
|
||||
title="{{'item.edit.bitstreams.bundle.edit.buttons.upload' | translate}}">
|
||||
|
@@ -3,6 +3,7 @@ import { Bundle } from '../../../../core/shared/bundle.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { ResponsiveColumnSizes } from '../../../../shared/responsive-table-sizes/responsive-column-sizes';
|
||||
import { ResponsiveTableSizes } from '../../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||
import { getItemPageRoute } from '../../../item-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-edit-bitstream-bundle',
|
||||
@@ -49,11 +50,17 @@ export class ItemEditBitstreamBundleComponent implements OnInit {
|
||||
*/
|
||||
bundleNameColumn: ResponsiveColumnSizes;
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute: string;
|
||||
|
||||
constructor(private viewContainerRef: ViewContainerRef) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.bundleNameColumn = this.columnSizes.combineColumns(0, 2);
|
||||
this.viewContainerRef.createEmbeddedView(this.bundleView);
|
||||
this.itemPageRoute = getItemPageRoute(this.item);
|
||||
}
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
const mockCollection = Object.assign(new Collection(), { id: 'collection1' });
|
||||
const mockItem: Item = Object.assign(new Item(), {
|
||||
id: '932c7d50-d85a-44cb-b9dc-b427b12877bd',
|
||||
uuid: '932c7d50-d85a-44cb-b9dc-b427b12877bd',
|
||||
name: 'test-item'
|
||||
});
|
||||
const mockItemRD: RemoteData<Item> = createSuccessfulRemoteDataObject(mockItem);
|
||||
@@ -212,7 +213,7 @@ describe('ItemCollectionMapperComponent', () => {
|
||||
});
|
||||
|
||||
it('should navigate to the item page', () => {
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/items/', mockItem.id]);
|
||||
expect(router.navigate).toHaveBeenCalledWith(['/items/' + mockItem.uuid]);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -26,6 +26,7 @@ import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-
|
||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||
import { getItemPageRoute } from '../../item-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-collection-mapper',
|
||||
@@ -312,7 +313,7 @@ export class ItemCollectionMapperComponent implements OnInit {
|
||||
getRemoteDataPayload(),
|
||||
take(1)
|
||||
).subscribe((item: Item) => {
|
||||
this.router.navigate(['/items/', item.id]);
|
||||
this.router.navigate([getItemPageRoute(item)]);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -89,7 +89,7 @@
|
||||
<button (click)="performAction()"
|
||||
class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
|
||||
</button>
|
||||
<button [routerLink]="['/items/', item.id, 'edit']" class="btn btn-outline-secondary cancel">
|
||||
<button [routerLink]="[itemPageRoute, 'edit']" class="btn btn-outline-secondary cancel">
|
||||
{{cancelMessage| translate}}
|
||||
</button>
|
||||
|
||||
|
@@ -183,7 +183,7 @@ describe('ItemDeleteComponent', () => {
|
||||
describe('notify', () => {
|
||||
it('should navigate to the item edit page on failed deletion of the item', () => {
|
||||
comp.notify(false);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute('fake-id')]);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute(mockItem)]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -355,7 +355,7 @@ export class ItemDeleteComponent
|
||||
this.router.navigate(['']);
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get('item.edit.' + this.messageKey + '.error'));
|
||||
this.router.navigate([getItemEditRoute(this.item.id)]);
|
||||
this.router.navigate([getItemEditRoute(this.item)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@
|
||||
</ds-dso-input-suggestions>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p>
|
||||
@@ -39,7 +39,7 @@
|
||||
{{'item.edit.move.processing' | translate}}
|
||||
</span>
|
||||
</button>
|
||||
<button [routerLink]="['/items/', (itemRD$ | async)?.payload?.id, 'edit']"
|
||||
<button [routerLink]="[(itemPageRoute$ | async), 'edit']"
|
||||
class="btn btn-outline-secondary">
|
||||
{{'item.edit.move.cancel' | translate}}
|
||||
</button>
|
||||
|
@@ -57,9 +57,9 @@ describe('ItemMoveComponent', () => {
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject(Object.assign(new Item(), {
|
||||
id: 'item1'
|
||||
})
|
||||
}))
|
||||
})
|
||||
};
|
||||
|
||||
@@ -122,7 +122,10 @@ describe('ItemMoveComponent', () => {
|
||||
});
|
||||
describe('moveCollection', () => {
|
||||
it('should call itemDataService.moveToCollection', () => {
|
||||
comp.itemId = 'item-id';
|
||||
comp.item = Object.assign(new Item(), {
|
||||
id: 'item-id',
|
||||
uuid: 'item-id',
|
||||
});
|
||||
comp.selectedCollectionName = 'selected-collection-id';
|
||||
comp.selectedCollection = collection1;
|
||||
comp.moveCollection();
|
||||
|
@@ -10,7 +10,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
getFirstSucceededRemoteData,
|
||||
getFirstCompletedRemoteData
|
||||
getFirstCompletedRemoteData, getAllSucceededRemoteDataPayload
|
||||
} from '../../../core/shared/operators';
|
||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||
import { Observable, of as observableOf } from 'rxjs';
|
||||
@@ -19,7 +19,7 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
import { SearchResult } from '../../../shared/search/search-result.model';
|
||||
import { getItemEditRoute } from '../../item-page-routing-paths';
|
||||
import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-move',
|
||||
@@ -43,11 +43,16 @@ export class ItemMoveComponent implements OnInit {
|
||||
selectedCollection: Collection;
|
||||
canSubmit = false;
|
||||
|
||||
itemId: string;
|
||||
item: Item;
|
||||
processing = false;
|
||||
|
||||
pagination = new PaginationComponentOptions();
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute$: Observable<string>;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private notificationsService: NotificationsService,
|
||||
@@ -58,8 +63,12 @@ export class ItemMoveComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemRD$ = this.route.data.pipe(map((data) => data.dso), getFirstSucceededRemoteData()) as Observable<RemoteData<Item>>;
|
||||
this.itemPageRoute$ = this.itemRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((item) => getItemPageRoute(item))
|
||||
);
|
||||
this.itemRD$.subscribe((rd) => {
|
||||
this.itemId = rd.payload.id;
|
||||
this.item = rd.payload;
|
||||
}
|
||||
);
|
||||
this.pagination.pageSize = 5;
|
||||
@@ -116,9 +125,9 @@ export class ItemMoveComponent implements OnInit {
|
||||
*/
|
||||
moveCollection() {
|
||||
this.processing = true;
|
||||
this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(getFirstCompletedRemoteData()).subscribe(
|
||||
this.itemDataService.moveToCollection(this.item.id, this.selectedCollection).pipe(getFirstCompletedRemoteData()).subscribe(
|
||||
(response: RemoteData<Collection>) => {
|
||||
this.router.navigate([getItemEditRoute(this.itemId)]);
|
||||
this.router.navigate([getItemEditRoute(this.item)]);
|
||||
if (response.hasSucceeded) {
|
||||
this.notificationsService.success(this.translateService.get('item.edit.move.success'));
|
||||
} else {
|
||||
|
@@ -47,9 +47,9 @@ describe('ItemReinstateComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject(Object.assign(new Item(), {
|
||||
id: 'fake-id'
|
||||
})
|
||||
}))
|
||||
})
|
||||
};
|
||||
|
||||
|
@@ -12,7 +12,7 @@
|
||||
{{'item.edit.tabs.status.labels.itemPage' | translate}}:
|
||||
</div>
|
||||
<div class="col-9 float-left status-data" id="status-itemPage">
|
||||
<a [routerLink]="getItemPage((itemRD$ | async)?.payload)">{{getItemPage((itemRD$ | async)?.payload)}}</a>
|
||||
<a [routerLink]="itemPageRoute$ | async">{{itemPageRoute$ | async}}</a>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let operation of (operations$ | async)" class="w-100" [ngClass]="{'pt-3': operation}">
|
||||
|
@@ -20,6 +20,7 @@ describe('ItemStatusComponent', () => {
|
||||
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: 'fake-id',
|
||||
uuid: 'fake-id',
|
||||
handle: 'fake/handle',
|
||||
lastModified: '2018',
|
||||
_links: {
|
||||
@@ -27,7 +28,7 @@ describe('ItemStatusComponent', () => {
|
||||
}
|
||||
});
|
||||
|
||||
const itemPageUrl = `items/${mockItem.id}`;
|
||||
const itemPageUrl = `/items/${mockItem.uuid}`;
|
||||
|
||||
const routeStub = {
|
||||
parent: {
|
||||
|
@@ -10,6 +10,7 @@ import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-path
|
||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { getAllSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-status',
|
||||
@@ -50,6 +51,11 @@ export class ItemStatusComponent implements OnInit {
|
||||
*/
|
||||
actionsKeys;
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute$: Observable<string>;
|
||||
|
||||
constructor(private route: ActivatedRoute,
|
||||
private authorizationService: AuthorizationDataService) {
|
||||
}
|
||||
@@ -109,15 +115,10 @@ export class ItemStatusComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url to the simple item page
|
||||
* @returns {string} url
|
||||
*/
|
||||
getItemPage(item: Item): string {
|
||||
return getItemPageRoute(item.id);
|
||||
this.itemPageRoute$ = this.itemRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((item) => getItemPageRoute(item))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,7 +126,7 @@ export class ItemStatusComponent implements OnInit {
|
||||
* @returns {string} url
|
||||
*/
|
||||
getCurrentUrl(item: Item): string {
|
||||
return getItemEditRoute(item.id);
|
||||
return getItemEditRoute(item);
|
||||
}
|
||||
|
||||
trackOperation(index: number, operation: ItemOperation) {
|
||||
|
@@ -6,11 +6,11 @@
|
||||
<ds-modify-item-overview [item]="item"></ds-modify-item-overview>
|
||||
<button (click)="performAction()" class="btn btn-outline-secondary perform-action">{{confirmMessage | translate}}
|
||||
</button>
|
||||
<button [routerLink]="['/items/', item.id, 'edit']" class="btn btn-outline-secondary cancel">
|
||||
<button [routerLink]="[itemPageRoute, 'edit']" class="btn btn-outline-secondary cancel">
|
||||
{{cancelMessage| translate}}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -74,9 +74,9 @@ describe('AbstractSimpleItemActionComponent', () => {
|
||||
|
||||
routeStub = {
|
||||
data: observableOf({
|
||||
dso: createSuccessfulRemoteDataObject({
|
||||
dso: createSuccessfulRemoteDataObject(Object.assign(new Item(), {
|
||||
id: 'fake-id'
|
||||
})
|
||||
}))
|
||||
})
|
||||
};
|
||||
|
||||
@@ -136,14 +136,14 @@ describe('AbstractSimpleItemActionComponent', () => {
|
||||
comp.processRestResponse(successfulRemoteData);
|
||||
|
||||
expect(notificationsServiceStub.success).toHaveBeenCalled();
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute(mockItem.id)]);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute(mockItem)]);
|
||||
});
|
||||
|
||||
it('should process a RemoteData to navigate and display success notification', () => {
|
||||
comp.processRestResponse(failedRemoteData);
|
||||
|
||||
expect(notificationsServiceStub.error).toHaveBeenCalled();
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute(mockItem.id)]);
|
||||
expect(routerStub.navigate).toHaveBeenCalledWith([getItemEditRoute(mockItem)]);
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -9,7 +9,7 @@ import { Observable } from 'rxjs';
|
||||
import { getFirstSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
import { findSuccessfulAccordingTo } from '../edit-item-operators';
|
||||
import { getItemEditRoute } from '../../item-page-routing-paths';
|
||||
import { getItemEditRoute, getItemPageRoute } from '../../item-page-routing-paths';
|
||||
|
||||
/**
|
||||
* Component to render and handle simple item edit actions such as withdrawal and reinstatement.
|
||||
@@ -30,6 +30,11 @@ export class AbstractSimpleItemActionComponent implements OnInit {
|
||||
headerMessage: string;
|
||||
descriptionMessage: string;
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute: string;
|
||||
|
||||
protected predicate: Predicate<RemoteData<Item>>;
|
||||
|
||||
constructor(protected route: ActivatedRoute,
|
||||
@@ -47,6 +52,7 @@ export class AbstractSimpleItemActionComponent implements OnInit {
|
||||
|
||||
this.itemRD$.pipe(first()).subscribe((rd) => {
|
||||
this.item = rd.payload;
|
||||
this.itemPageRoute = getItemPageRoute(this.item);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -71,11 +77,11 @@ export class AbstractSimpleItemActionComponent implements OnInit {
|
||||
this.itemDataService.findById(this.item.id).pipe(
|
||||
findSuccessfulAccordingTo(this.predicate)).subscribe(() => {
|
||||
this.notificationsService.success(this.translateService.get('item.edit.' + this.messageKey + '.success'));
|
||||
this.router.navigate([getItemEditRoute(this.item.id)]);
|
||||
this.router.navigate([getItemEditRoute(this.item)]);
|
||||
});
|
||||
} else {
|
||||
this.notificationsService.error(this.translateService.get('item.edit.' + this.messageKey + '.error'));
|
||||
this.router.navigate([getItemEditRoute(this.item.id)]);
|
||||
this.router.navigate([getItemEditRoute(this.item)]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,11 +7,11 @@
|
||||
<div class="d-flex flex-row">
|
||||
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="simple-view-link my-3">
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
|
||||
{{"item.page.link.simple" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -18,9 +18,8 @@ import { hasValue } from '../../shared/empty.util';
|
||||
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.
|
||||
* All fields of the item that should be displayed, are defined in its template.
|
||||
*/
|
||||
|
||||
@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,4 +1,6 @@
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
import { Item } from '../core/shared/item.model';
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
|
||||
export const ITEM_MODULE_PATH = 'items';
|
||||
|
||||
@@ -6,12 +8,30 @@ export function getItemModuleRoute() {
|
||||
return `/${ITEM_MODULE_PATH}`;
|
||||
}
|
||||
|
||||
export function getItemPageRoute(itemId: string) {
|
||||
return new URLCombiner(getItemModuleRoute(), itemId).toString();
|
||||
/**
|
||||
* Get the route to an item's page
|
||||
* Depending on the item's relationship type, the route will either start with /items or /entities
|
||||
* @param item The item to retrieve the route for
|
||||
*/
|
||||
export function getItemPageRoute(item: Item) {
|
||||
const type = item.firstMetadataValue('relationship.type');
|
||||
return getEntityPageRoute(type, item.uuid);
|
||||
}
|
||||
|
||||
export function getItemEditRoute(id: string) {
|
||||
return new URLCombiner(getItemModuleRoute(), id, ITEM_EDIT_PATH).toString();
|
||||
export function getItemEditRoute(item: Item) {
|
||||
return new URLCombiner(getItemPageRoute(item), ITEM_EDIT_PATH).toString();
|
||||
}
|
||||
|
||||
export function getEntityPageRoute(entityType: string, itemId: string) {
|
||||
if (isNotEmpty(entityType)) {
|
||||
return new URLCombiner('/entities', encodeURIComponent(entityType.toLowerCase()), itemId).toString();
|
||||
} else {
|
||||
return new URLCombiner(getItemModuleRoute(), itemId).toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function getEntityEditRoute(entityType: string, itemId: string) {
|
||||
return new URLCombiner(getEntityPageRoute(entityType, itemId), ITEM_EDIT_PATH).toString();
|
||||
}
|
||||
|
||||
export const ITEM_EDIT_PATH = 'edit';
|
||||
|
@@ -1,18 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
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 { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
|
||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||
import { LinkService } from '../core/cache/builders/link.service';
|
||||
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 { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||
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({
|
||||
imports: [
|
||||
@@ -27,18 +26,17 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: ItemPageComponent,
|
||||
component: ThemedItemPageComponent,
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'full',
|
||||
component: FullItemPageComponent,
|
||||
component: ThemedFullItemPageComponent,
|
||||
},
|
||||
{
|
||||
path: ITEM_EDIT_PATH,
|
||||
loadChildren: () => import('./edit-item-page/edit-item-page.module')
|
||||
.then((m) => m.EditItemPageModule),
|
||||
canActivate: [ItemPageAdministratorGuard]
|
||||
},
|
||||
{
|
||||
path: UPLOAD_BITSTREAM_PATH,
|
||||
@@ -68,7 +66,7 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
ItemBreadcrumbResolver,
|
||||
DSOBreadcrumbsService,
|
||||
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 { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-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 = [
|
||||
// put only entry components that use custom decorator
|
||||
@@ -34,7 +36,9 @@ const ENTRY_COMPONENTS = [
|
||||
|
||||
const DECLARATIONS = [
|
||||
ItemPageComponent,
|
||||
ThemedItemPageComponent,
|
||||
FullItemPageComponent,
|
||||
ThemedFullItemPageComponent,
|
||||
MetadataUriValuesComponent,
|
||||
ItemPageAuthorFieldComponent,
|
||||
ItemPageDateFieldComponent,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { ItemDataService } from '../core/data/item-data.service';
|
||||
@@ -9,6 +9,9 @@ import { FindListOptions } from '../core/data/request.models';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { getItemPageRoute } from './item-page-routing-paths';
|
||||
|
||||
/**
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
@@ -31,7 +34,8 @@ export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
||||
export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
constructor(
|
||||
private itemService: ItemDataService,
|
||||
private store: Store<any>
|
||||
private store: Store<any>,
|
||||
private router: Router
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -49,6 +53,18 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
...ITEM_PAGE_LINKS_TO_FOLLOW
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
map((rd: RemoteData<Item>) => {
|
||||
if (rd.hasSucceeded && hasValue(rd.payload)) {
|
||||
const itemRoute = getItemPageRoute(rd.payload);
|
||||
const thisRoute = state.url;
|
||||
if (!thisRoute.startsWith(itemRoute)) {
|
||||
const itemId = rd.payload.uuid;
|
||||
const subRoute = thisRoute.substring(thisRoute.indexOf(itemId) + itemId.length, thisRoute.length);
|
||||
this.router.navigateByUrl(itemRoute + subRoute);
|
||||
}
|
||||
}
|
||||
return rd;
|
||||
})
|
||||
);
|
||||
|
||||
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||
|
@@ -11,9 +11,10 @@ import { Item } from '../../core/shared/item.model';
|
||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||
|
||||
import { fadeInOut } from '../../shared/animations/fade';
|
||||
import { redirectOn4xx } from '../../core/shared/operators';
|
||||
import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators';
|
||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { getItemPageRoute } from '../item-page-routing-paths';
|
||||
|
||||
/**
|
||||
* This component renders a simple item page.
|
||||
@@ -44,6 +45,11 @@ export class ItemPageComponent implements OnInit {
|
||||
*/
|
||||
viewMode = ViewMode.StandalonePage;
|
||||
|
||||
/**
|
||||
* Route to the item's page
|
||||
*/
|
||||
itemPageRoute$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
@@ -61,5 +67,9 @@ export class ItemPageComponent implements OnInit {
|
||||
redirectOn4xx(this.router, this.authService)
|
||||
);
|
||||
this.metadataService.processRemoteData(this.itemRD$);
|
||||
this.itemPageRoute$ = this.itemRD$.pipe(
|
||||
getAllSucceededRemoteDataPayload(),
|
||||
map((item) => getItemPageRoute(item))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -74,7 +74,7 @@
|
||||
</ds-item-page-uri-field>
|
||||
<ds-item-page-collections [item]="object"></ds-item-page-collections>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + object.id + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||
import { Bitstream } from '../../../../core/shared/bitstream.model';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
|
||||
import { getItemPageRoute } from '../../../item-page-routing-paths';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item',
|
||||
@@ -12,12 +13,21 @@ import { getFirstSucceededRemoteDataPayload } from '../../../../core/shared/oper
|
||||
/**
|
||||
* A generic component for displaying metadata and relations of an item
|
||||
*/
|
||||
export class ItemComponent {
|
||||
export class ItemComponent implements OnInit {
|
||||
@Input() object: Item;
|
||||
|
||||
/**
|
||||
* Route to the item page
|
||||
*/
|
||||
itemPageRoute: string;
|
||||
|
||||
constructor(protected bitstreamDataService: BitstreamDataService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.itemPageRoute = getItemPageRoute(this.object);
|
||||
}
|
||||
|
||||
// TODO refactor to return RemoteData, and thumbnail template to deal with loading
|
||||
getThumbnail(): Observable<Bitstream> {
|
||||
return this.bitstreamDataService.getThumbnailFor(this.object).pipe(
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||
</h2>
|
||||
<div class="pl-2">
|
||||
<ds-dso-page-edit-button [pageRoutePrefix]="'items'" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@@ -59,7 +59,7 @@
|
||||
</ds-item-page-uri-field>
|
||||
<ds-item-page-collections [item]="object"></ds-item-page-collections>
|
||||
<div>
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + object.id + '/full']">
|
||||
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']">
|
||||
{{"item.page.link.full" | translate}}
|
||||
</a>
|
||||
</div>
|
||||
|
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 { RouterModule } from '@angular/router';
|
||||
|
||||
import { LoginPageComponent } from './login-page.component';
|
||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { ThemedLoginPageComponent } from './themed-login-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
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: [
|
||||
|
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { LoginPageComponent } from './login-page.component';
|
||||
import { LoginPageRoutingModule } from './login-page-routing.module';
|
||||
import { ThemedLoginPageComponent } from './themed-login-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -11,7 +12,8 @@ import { LoginPageRoutingModule } from './login-page-routing.module';
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
LoginPageComponent
|
||||
LoginPageComponent,
|
||||
ThemedLoginPageComponent
|
||||
]
|
||||
})
|
||||
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 { RouterModule } from '@angular/router';
|
||||
|
||||
import { LogoutPageComponent } from './logout-page.component';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { ThemedLogoutPageComponent } from './themed-logout-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -10,7 +9,7 @@ import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: '',
|
||||
component: LogoutPageComponent,
|
||||
component: ThemedLogoutPageComponent,
|
||||
data: { title: 'logout.title' }
|
||||
}
|
||||
])
|
||||
|
@@ -3,6 +3,7 @@ import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { LogoutPageComponent } from './logout-page.component';
|
||||
import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
||||
import { ThemedLogoutPageComponent } from './themed-logout-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -11,7 +12,8 @@ import { LogoutPageRoutingModule } from './logout-page-routing.module';
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
LogoutPageComponent
|
||||
LogoutPageComponent,
|
||||
ThemedLogoutPageComponent
|
||||
]
|
||||
})
|
||||
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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, UrlSegment } from '@angular/router';
|
||||
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
||||
import { isNotEmpty } from '../shared/empty.util';
|
||||
import { ThemedObjectNotFoundComponent } from './objectnotfound/themed-objectnotfound.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -10,7 +10,7 @@ import { isNotEmpty } from '../shared/empty.util';
|
||||
{
|
||||
matcher: urlMatcher,
|
||||
canActivate: [LookupGuard],
|
||||
component: ObjectNotFoundComponent }
|
||||
component: ThemedObjectNotFoundComponent }
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
|
@@ -4,6 +4,7 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { LookupRoutingModule } from './lookup-by-id-routing.module';
|
||||
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
||||
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||
import { ThemedObjectNotFoundComponent } from './objectnotfound/themed-objectnotfound.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -12,7 +13,8 @@ import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||
SharedModule,
|
||||
],
|
||||
declarations: [
|
||||
ObjectNotFoundComponent
|
||||
ObjectNotFoundComponent,
|
||||
ThemedObjectNotFoundComponent
|
||||
],
|
||||
providers: [
|
||||
DsoRedirectDataService
|
||||
|
@@ -22,7 +22,7 @@ describe('LookupGuard', () => {
|
||||
}
|
||||
};
|
||||
guard.canActivate(scopedRoute as any, undefined);
|
||||
expect(dsoService.findByIdAndIDType).toHaveBeenCalledWith('123456789/1234', IdentifierType.HANDLE);
|
||||
expect(dsoService.findByIdAndIDType).toHaveBeenCalledWith('hdl:123456789/1234', IdentifierType.HANDLE);
|
||||
});
|
||||
|
||||
it('should call findByIdAndIDType with handle params', () => {
|
||||
@@ -33,7 +33,7 @@ describe('LookupGuard', () => {
|
||||
}
|
||||
};
|
||||
guard.canActivate(scopedRoute as any, undefined);
|
||||
expect(dsoService.findByIdAndIDType).toHaveBeenCalledWith('123456789%2F1234', IdentifierType.HANDLE);
|
||||
expect(dsoService.findByIdAndIDType).toHaveBeenCalledWith('hdl:123456789%2F1234', IdentifierType.HANDLE);
|
||||
});
|
||||
|
||||
it('should call findByIdAndIDType with UUID params', () => {
|
||||
|
@@ -35,11 +35,11 @@ export class LookupGuard implements CanActivate {
|
||||
type = IdentifierType.HANDLE;
|
||||
const prefix = route.params.idType;
|
||||
const handleId = route.params.id;
|
||||
id = `${prefix}/${handleId}`;
|
||||
id = `hdl:${prefix}/${handleId}`;
|
||||
|
||||
} else if (route.params.idType === IdentifierType.HANDLE) {
|
||||
type = IdentifierType.HANDLE;
|
||||
id = route.params.id;
|
||||
id = 'hdl:' + route.params.id;
|
||||
|
||||
} else {
|
||||
type = IdentifierType.UUID;
|
||||
|
@@ -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 { RouterModule } from '@angular/router';
|
||||
|
||||
import { MyDSpacePageComponent } from './my-dspace-page.component';
|
||||
import { MyDSpaceGuard } from './my-dspace.guard';
|
||||
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: MyDSpacePageComponent,
|
||||
component: ThemedMyDSpacePageComponent,
|
||||
data: { title: 'mydspace.title' },
|
||||
canActivate: [
|
||||
MyDSpaceGuard
|
||||
|
@@ -11,6 +11,15 @@ import { MyDSpaceGuard } from './my-dspace.guard';
|
||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||
import { CollectionSelectorComponent } from './collection-selector/collection-selector.component';
|
||||
import { MyDspaceSearchModule } from './my-dspace-search.module';
|
||||
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
|
||||
|
||||
const DECLARATIONS = [
|
||||
MyDSpacePageComponent,
|
||||
ThemedMyDSpacePageComponent,
|
||||
MyDSpaceResultsComponent,
|
||||
MyDSpaceNewSubmissionComponent,
|
||||
CollectionSelectorComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -19,16 +28,12 @@ import { MyDspaceSearchModule } from './my-dspace-search.module';
|
||||
MyDspacePageRoutingModule,
|
||||
MyDspaceSearchModule.withEntryComponents()
|
||||
],
|
||||
declarations: [
|
||||
MyDSpacePageComponent,
|
||||
MyDSpaceResultsComponent,
|
||||
MyDSpaceNewSubmissionComponent,
|
||||
CollectionSelectorComponent
|
||||
],
|
||||
declarations: DECLARATIONS,
|
||||
providers: [
|
||||
MyDSpaceGuard,
|
||||
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`);
|
||||
}
|
||||
}
|
@@ -1,7 +1,14 @@
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Inject,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
|
@@ -2,11 +2,11 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
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 { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||
import { SearchPageModule } from './search-page.module';
|
||||
import { ThemedSearchPageComponent } from './themed-search-page.component';
|
||||
import { ThemedConfigurationSearchPageComponent } from './themed-configuration-search-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -15,8 +15,8 @@ import { SearchPageModule } from './search-page.module';
|
||||
path: '',
|
||||
resolve: { breadcrumb: I18nBreadcrumbResolver }, data: { title: 'search.title', breadcrumbKey: 'search' },
|
||||
children: [
|
||||
{ path: '', component: SearchPageComponent },
|
||||
{ path: ':configuration', component: ConfigurationSearchPageComponent, canActivate: [ConfigurationSearchPageGuard] }
|
||||
{ path: '', component: ThemedSearchPageComponent },
|
||||
{ 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 { JournalEntitiesModule } from '../entity-groups/journal-entities/journal-entities.module';
|
||||
import { ResearchEntitiesModule } from '../entity-groups/research-entities/research-entities.module';
|
||||
import { ThemedSearchPageComponent } from './themed-search-page.component';
|
||||
|
||||
const components = [
|
||||
SearchPageComponent,
|
||||
SearchComponent,
|
||||
SearchTrackerComponent
|
||||
SearchTrackerComponent,
|
||||
ThemedSearchPageComponent
|
||||
];
|
||||
|
||||
@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 { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { SubmissionSubmitComponent } from '../submission/submit/submission-submit.component';
|
||||
import { ThemedSubmissionSubmitComponent } from '../submission/submit/themed-submission-submit.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -11,7 +11,7 @@ import { SubmissionSubmitComponent } from '../submission/submit/submission-submi
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: SubmissionSubmitComponent,
|
||||
component: ThemedSubmissionSubmitComponent,
|
||||
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 { 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 { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component';
|
||||
import {
|
||||
WORKFLOW_ITEM_SEND_BACK_PATH,
|
||||
WORKFLOW_ITEM_DELETE_PATH,
|
||||
WORKFLOW_ITEM_EDIT_PATH
|
||||
} from './workflowitems-edit-page-routing-paths';
|
||||
import { WORKFLOW_ITEM_DELETE_PATH, WORKFLOW_ITEM_EDIT_PATH, WORKFLOW_ITEM_SEND_BACK_PATH } from './workflowitems-edit-page-routing-paths';
|
||||
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.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({
|
||||
imports: [
|
||||
@@ -22,19 +18,19 @@ import {
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: WORKFLOW_ITEM_EDIT_PATH,
|
||||
component: SubmissionEditComponent,
|
||||
component: ThemedSubmissionEditComponent,
|
||||
data: { title: 'submission.edit.title' }
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: WORKFLOW_ITEM_DELETE_PATH,
|
||||
component: WorkflowItemDeleteComponent,
|
||||
component: ThemedWorkflowItemDeleteComponent,
|
||||
data: { title: 'workflow-item.delete.title' }
|
||||
},
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: WORKFLOW_ITEM_SEND_BACK_PATH,
|
||||
component: WorkflowItemSendBackComponent,
|
||||
component: ThemedWorkflowItemSendBackComponent,
|
||||
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 { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-item-delete.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({
|
||||
imports: [
|
||||
@@ -13,7 +15,7 @@ import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflo
|
||||
SharedModule,
|
||||
SubmissionModule,
|
||||
],
|
||||
declarations: [WorkflowItemDeleteComponent, WorkflowItemSendBackComponent]
|
||||
declarations: [WorkflowItemDeleteComponent, ThemedWorkflowItemDeleteComponent, WorkflowItemSendBackComponent, ThemedWorkflowItemSendBackComponent]
|
||||
})
|
||||
/**
|
||||
* 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 { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { SubmissionEditComponent } from '../submission/edit/submission-edit.component';
|
||||
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -11,7 +11,7 @@ import { SubmissionEditComponent } from '../submission/edit/submission-edit.comp
|
||||
{
|
||||
canActivate: [AuthenticatedGuard],
|
||||
path: ':id/edit',
|
||||
component: SubmissionEditComponent,
|
||||
component: ThemedSubmissionEditComponent,
|
||||
data: { title: 'submission.edit.title' }
|
||||
}
|
||||
])
|
||||
|
@@ -53,7 +53,7 @@ export function getDSORoute(dso: DSpaceObject): string {
|
||||
case Collection.type.value:
|
||||
return getCollectionPageRoute(dso.uuid);
|
||||
case Item.type.value:
|
||||
return getItemPageRoute(dso.uuid);
|
||||
return getItemPageRoute(dso as Item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,19 +2,9 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||
|
||||
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
|
||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
|
||||
import {
|
||||
ADMIN_MODULE_PATH,
|
||||
BITSTREAM_MODULE_PATH,
|
||||
FORBIDDEN_PATH,
|
||||
FORGOT_PASSWORD_PATH,
|
||||
INFO_MODULE_PATH,
|
||||
PROFILE_MODULE_PATH,
|
||||
REGISTER_PATH,
|
||||
WORKFLOW_ITEM_MODULE_PATH
|
||||
} from './app-routing-paths';
|
||||
import { ADMIN_MODULE_PATH, BITSTREAM_MODULE_PATH, FORBIDDEN_PATH, FORGOT_PASSWORD_PATH, INFO_MODULE_PATH, PROFILE_MODULE_PATH, REGISTER_PATH, WORKFLOW_ITEM_MODULE_PATH } from './app-routing-paths';
|
||||
import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths';
|
||||
import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths';
|
||||
import { ITEM_MODULE_PATH } from './+item-page/item-page-routing-paths';
|
||||
@@ -22,7 +12,8 @@ import { PROCESS_MODULE_PATH } from './process-page/process-page-routing.paths';
|
||||
import { ReloadGuard } from './core/reload/reload.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 { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
|
||||
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -30,7 +21,7 @@ import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
path: '', canActivate: [AuthBlockingGuard],
|
||||
children: [
|
||||
{ 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',
|
||||
loadChildren: () => import('./+home-page/home-page.module')
|
||||
@@ -86,6 +77,11 @@ import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
.then((m) => m.ItemPageModule),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
||||
},
|
||||
{ path: 'entities/:entity-type',
|
||||
loadChildren: () => import('./+item-page/item-page.module')
|
||||
.then((m) => m.ItemPageModule),
|
||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
||||
},
|
||||
{
|
||||
path: BITSTREAM_MODULE_PATH,
|
||||
loadChildren: () => import('./+bitstream-page/bitstream-page.module')
|
||||
@@ -168,14 +164,14 @@ import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
},
|
||||
{
|
||||
path: FORBIDDEN_PATH,
|
||||
component: ForbiddenComponent
|
||||
component: ThemedForbiddenComponent
|
||||
},
|
||||
{
|
||||
path: 'statistics',
|
||||
loadChildren: () => import('./statistics-page/statistics-page-routing.module')
|
||||
.then((m) => m.StatisticsPageRoutingModule),
|
||||
},
|
||||
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||
]}
|
||||
],{
|
||||
onSameUrlNavigation: 'reload',
|
||||
|
@@ -35,6 +35,7 @@ import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -45,47 +46,54 @@ const initialState = {
|
||||
|
||||
describe('App component', () => {
|
||||
|
||||
let breadcrumbsServiceSpy;
|
||||
|
||||
function getMockLocaleService(): LocaleService {
|
||||
return jasmine.createSpyObj('LocaleService', {
|
||||
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
||||
});
|
||||
}
|
||||
|
||||
const defaultTestBedConf = {
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [AppComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
{ provide: MenuService, useValue: menuService },
|
||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||
provideMockStore({ initialState }),
|
||||
AppComponent,
|
||||
RouteService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
const getDefaultTestBedConf = () => {
|
||||
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
||||
|
||||
return {
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [AppComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
{ provide: MenuService, useValue: menuService },
|
||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||
{ provide: LocaleService, useValue: getMockLocaleService() },
|
||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||
{ provide: BreadcrumbsService, useValue: breadcrumbsServiceSpy },
|
||||
provideMockStore({ initialState }),
|
||||
AppComponent,
|
||||
RouteService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
};
|
||||
};
|
||||
|
||||
// waitForAsync beforeEach
|
||||
beforeEach(waitForAsync(() => {
|
||||
return TestBed.configureTestingModule(defaultTestBedConf);
|
||||
return TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||
}));
|
||||
|
||||
// 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', () => {
|
||||
let googleAnalyticsSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
||||
TestBed.resetTestingModule();
|
||||
TestBed.configureTestingModule(defaultTestBedConf);
|
||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
||||
'addTrackingIdToPage',
|
||||
]);
|
||||
@@ -154,7 +168,7 @@ describe('App component', () => {
|
||||
beforeEach(() => {
|
||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
||||
TestBed.resetTestingModule();
|
||||
TestBed.configureTestingModule(defaultTestBedConf);
|
||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
||||
document = TestBed.inject(DOCUMENT);
|
||||
headSpy = jasmine.createSpyObj('head', ['appendChild']);
|
||||
|
@@ -36,6 +36,7 @@ import { DOCUMENT } from '@angular/common';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
@@ -73,6 +74,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
private menuService: MenuService,
|
||||
private windowService: HostWindowService,
|
||||
private localeService: LocaleService,
|
||||
private breadcrumbsService: BreadcrumbsService,
|
||||
@Optional() private cookiesService: KlaroService,
|
||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
||||
) {
|
||||
@@ -106,6 +108,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
angulartics2DSpace.startTracking();
|
||||
|
||||
metadata.listenForRouteChange();
|
||||
breadcrumbsService.listenForRouteChanges();
|
||||
|
||||
if (environment.debug) {
|
||||
console.info(environment);
|
||||
|
@@ -46,6 +46,11 @@ import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||
import { RootComponent } from './root/root.component';
|
||||
import { ThemedRootComponent } from './root/themed-root.component';
|
||||
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() {
|
||||
return environment.ui.nameSpace;
|
||||
@@ -127,17 +132,22 @@ const DECLARATIONS = [
|
||||
RootComponent,
|
||||
ThemedRootComponent,
|
||||
HeaderComponent,
|
||||
ThemedHeaderComponent,
|
||||
HeaderNavbarWrapperComponent,
|
||||
AdminSidebarComponent,
|
||||
AdminSidebarSectionComponent,
|
||||
ExpandableAdminSidebarSectionComponent,
|
||||
FooterComponent,
|
||||
ThemedFooterComponent,
|
||||
PageNotFoundComponent,
|
||||
ThemedPageNotFoundComponent,
|
||||
NotificationComponent,
|
||||
NotificationsBoardComponent,
|
||||
SearchNavbarComponent,
|
||||
BreadcrumbsComponent,
|
||||
ThemedBreadcrumbsComponent,
|
||||
ForbiddenComponent,
|
||||
ThemedForbiddenComponent,
|
||||
];
|
||||
|
||||
const EXPORTS = [
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { BreadcrumbsService } from '../../core/breadcrumbs/breadcrumbs.service';
|
||||
import { BreadcrumbsProviderService } from '../../core/breadcrumbs/breadcrumbsProviderService';
|
||||
|
||||
/**
|
||||
* Interface for breadcrumb configuration objects
|
||||
@@ -7,7 +7,7 @@ export interface BreadcrumbConfig<T> {
|
||||
/**
|
||||
* 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
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user