94390: Replace DSO page edit buttons with a menu

This commit is contained in:
Yana De Pauw
2022-09-20 15:45:04 +02:00
parent ca87f09625
commit 878db5be75
62 changed files with 1247 additions and 585 deletions

View File

@@ -1,7 +1,7 @@
<div class="sidebar-section">
<a class="nav-item nav-link d-flex flex-row flex-nowrap"
[ngClass]="{ disabled: !hasLink }"
[attr.aria-disabled]="!hasLink"
[ngClass]="{ disabled: isDisabled }"
[attr.aria-disabled]="isDisabled"
[attr.aria-labelledby]="'sidebarName-' + section.id"
[title]="('menu.section.icon.' + section.id) | translate"
[routerLink]="itemModel.link"

View File

@@ -17,6 +17,8 @@ describe('AdminSidebarSectionComponent', () => {
const menuService = new MenuServiceStub();
const iconString = 'test';
describe('when not disabled', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot()],
@@ -49,6 +51,52 @@ describe('AdminSidebarSectionComponent', () => {
const icon = fixture.debugElement.query(By.css('.shortcut-icon')).query(By.css('i.fas'));
expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
});
it('should not contain the disabled class', () => {
const disabled = fixture.debugElement.query(By.css('.disabled'));
expect(disabled).toBeFalsy();
});
});
describe('when disabled', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot()],
declarations: [AdminSidebarSectionComponent, TestComponent],
providers: [
{provide: 'sectionDataProvider', useValue: {model: {link: 'google.com', disabled: true}, icon: iconString}},
{provide: MenuService, useValue: menuService},
{provide: CSSVariableService, useClass: CSSVariableServiceStub},
]
}).overrideComponent(AdminSidebarSectionComponent, {
set: {
entryComponents: [TestComponent]
}
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AdminSidebarSectionComponent);
component = fixture.componentInstance;
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should set the right icon', () => {
const icon = fixture.debugElement.query(By.css('.shortcut-icon')).query(By.css('i.fas'));
expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
});
it('should contain the disabled class', () => {
const disabled = fixture.debugElement.query(By.css('.disabled'));
expect(disabled).toBeTruthy();
});
});
});
// declare a test component

View File

@@ -5,7 +5,7 @@ import { MenuService } from '../../../shared/menu/menu.service';
import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
import { MenuSection } from '../../../shared/menu/menu.reducer';
import { isNotEmpty } from '../../../shared/empty.util';
import { isEmpty } from '../../../shared/empty.util';
import { Router } from '@angular/router';
/**
@@ -26,7 +26,12 @@ export class AdminSidebarSectionComponent extends MenuSectionComponent implement
*/
menuID: MenuID = MenuID.ADMIN;
itemModel;
hasLink: boolean;
/**
* Boolean to indicate whether this section is disabled
*/
isDisabled: boolean;
constructor(
@Inject('sectionDataProvider') menuSection: MenuSection,
protected menuService: MenuService,
@@ -38,13 +43,13 @@ export class AdminSidebarSectionComponent extends MenuSectionComponent implement
}
ngOnInit(): void {
this.hasLink = isNotEmpty(this.itemModel?.link);
this.isDisabled = this.itemModel?.disabled || isEmpty(this.itemModel?.link);
super.ngOnInit();
}
navigate(event: any): void {
event.preventDefault();
if (this.hasLink) {
if (this.isDisabled) {
this.router.navigate(this.itemModel.link);
}
}

View File

@@ -7,6 +7,7 @@
[attr.aria-labelledby]="'sidebarName-' + section.id"
[attr.aria-expanded]="expanded | async"
[title]="('menu.section.icon.' + section.id) | translate"
[class.disabled]="section.model.disabled"
(click)="toggleSection($event)"
(keyup.space)="toggleSection($event)"
(keyup.enter)="toggleSection($event)"

View File

@@ -21,6 +21,7 @@ import { CollectionPageAdministratorGuard } from './collection-page-administrato
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';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
@NgModule({
imports: [
@@ -34,7 +35,8 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
path: ':id',
resolve: {
dso: CollectionPageResolver,
breadcrumb: CollectionBreadcrumbResolver
breadcrumb: CollectionBreadcrumbResolver,
menu: DSOEditMenuResolver
},
runGuardsAndResolvers: 'always',
children: [
@@ -90,7 +92,8 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
DSOBreadcrumbsService,
LinkService,
CreateCollectionPageGuard,
CollectionPageAdministratorGuard
CollectionPageAdministratorGuard,
DSOEditMenuResolver
]
})
export class CollectionPageRoutingModule {

View File

@@ -34,9 +34,7 @@
[title]="'collection.page.news'">
</ds-comcol-page-content>
</header>
<div class="pl-2">
<ds-dso-page-edit-button *ngIf="isCollectionAdmin$ | async" [pageRoute]="collectionPageRoute$ | async" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<section class="comcol-page-browse-section">
<!-- Browse-By Links -->

View File

@@ -14,6 +14,7 @@ import { CommunityPageAdministratorGuard } from './community-page-administrator.
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';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
@NgModule({
imports: [
@@ -27,7 +28,8 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
path: ':id',
resolve: {
dso: CommunityPageResolver,
breadcrumb: CommunityBreadcrumbResolver
breadcrumb: CommunityBreadcrumbResolver,
menu: DSOEditMenuResolver
},
runGuardsAndResolvers: 'always',
children: [
@@ -72,7 +74,8 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
DSOBreadcrumbsService,
LinkService,
CreateCommunityPageGuard,
CommunityPageAdministratorGuard
CommunityPageAdministratorGuard,
DSOEditMenuResolver
]
})
export class CommunityPageRoutingModule {

View File

@@ -20,9 +20,7 @@
[title]="'community.page.news'">
</ds-comcol-page-content>
</header>
<div class="pl-2">
<ds-dso-page-edit-button *ngIf="isCommunityAdmin$ | async" [pageRoute]="communityPageRoute$ | async" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<section class="comcol-page-browse-section">
<!-- Browse-By Links -->

View File

@@ -2,9 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'journalissue.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,9 +2,7 @@
<h2 class="item-page-title-field mr-auto">
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'journalvolume.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,9 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'journal.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,9 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'orgunit.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['organization.legalName'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'orgunit.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,9 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'person.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="[object?.firstMetadata('person.familyName'), object?.firstMetadata('person.givenName')]" [separator]="', '"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'person.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,9 +2,8 @@
<h2 class="item-page-title-field mr-auto">
{{'project.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'project.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -6,9 +6,8 @@
<ds-view-tracker [object]="item"></ds-view-tracker>
<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 [pageRoute]="itemPageRoute$ | async" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="simple-view-link my-3" *ngIf="!fromWfi">
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">

View File

@@ -16,6 +16,7 @@ import { ThemedFullItemPageComponent } from './full/themed-full-item-page.compon
import { VersionPageComponent } from './version-page/version-page/version-page.component';
import { BitstreamRequestACopyPageComponent } from '../shared/bitstream-request-a-copy-page/bitstream-request-a-copy-page.component';
import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
@NgModule({
imports: [
@@ -24,7 +25,8 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths';
path: ':id',
resolve: {
dso: ItemPageResolver,
breadcrumb: ItemBreadcrumbResolver
breadcrumb: ItemBreadcrumbResolver,
menu: DSOEditMenuResolver
},
runGuardsAndResolvers: 'always',
children: [
@@ -88,6 +90,7 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths';
LinkService,
ItemPageAdministratorGuard,
VersionResolver,
DSOEditMenuResolver
]
})

View File

@@ -32,7 +32,6 @@ import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/med
import { NgxGalleryModule } from '@kolkov/ngx-gallery';
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
import { ThemedFileSectionComponent } from './simple/field-components/file-section/themed-file-section.component';
@@ -82,7 +81,6 @@ const DECLARATIONS = [
],
declarations: [
...DECLARATIONS,
VersionedItemComponent
],
exports: [
...DECLARATIONS

View File

@@ -11,9 +11,7 @@
<h2 class="item-page-title-field mr-auto">
{{'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 [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'publication.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -11,12 +11,8 @@
<h2 class="item-page-title-field mr-auto">
<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
</h2>
<div class="pl-2">
<ds-dso-page-version-button (newVersionEvent)="onCreateNewVersion()" [dso]="object"
[tooltipMsgCreate]="'item.page.version.create'"
[tooltipMsgHasDraft]="'item.page.version.hasDraft'"></ds-dso-page-version-button>
<ds-dso-page-edit-button [pageRoute]="itemPageRoute" [dso]="object" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
<ds-dso-edit-menu></ds-dso-edit-menu>
</div>
<div class="row">
<div class="col-xs-12 col-md-4">

View File

@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
import { ViewMode } from '../../../../core/shared/view-mode.model';
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
import { VersionedItemComponent } from '../versioned-item/versioned-item.component';
import { ItemComponent } from '../shared/item.component';
/**
* Component that represents a publication Item page
@@ -15,6 +15,6 @@ import { VersionedItemComponent } from '../versioned-item/versioned-item.compone
templateUrl: './untyped-item.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UntypedItemComponent extends VersionedItemComponent {
export class UntypedItemComponent extends ItemComponent {
}

View File

@@ -1,95 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VersionedItemComponent } from './versioned-item.component';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { Item } from '../../../../core/shared/item.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { MetadataMap } from '../../../../core/shared/metadata.models';
import { createRelationshipsObservable, mockRouteService } from '../shared/item.component.spec';
import { RouterTestingModule } from '@angular/router/testing';
import { Component } from '@angular/core';
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
import { SearchService } from '../../../../core/shared/search/search.service';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Version } from '../../../../core/shared/version.model';
import { RouteService } from '../../../../core/services/route.service';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
metadata: new MetadataMap(),
relationships: createRelationshipsObservable(),
_links: {
self: {
href: 'item-href'
},
version: {
href: 'version-href'
}
}
});
@Component({template: ''})
class DummyComponent {
}
describe('VersionedItemComponent', () => {
let component: VersionedItemComponent;
let fixture: ComponentFixture<VersionedItemComponent>;
let versionService: VersionDataService;
let versionHistoryService: VersionHistoryDataService;
const versionServiceSpy = jasmine.createSpyObj('versionService', {
findByHref: createSuccessfulRemoteDataObject$<Version>(new Version()),
});
const versionHistoryServiceSpy = jasmine.createSpyObj('versionHistoryService', {
createVersion: createSuccessfulRemoteDataObject$<Version>(new Version()),
});
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [VersionedItemComponent, DummyComponent],
imports: [RouterTestingModule],
providers: [
{ provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy },
{ provide: TranslateService, useValue: {} },
{ provide: VersionDataService, useValue: versionServiceSpy },
{ provide: NotificationsService, useValue: {} },
{ provide: ItemVersionsSharedService, useValue: {} },
{ provide: WorkspaceitemDataService, useValue: {} },
{ provide: SearchService, useValue: {} },
{ provide: ItemDataService, useValue: {} },
{ provide: RouteService, useValue: mockRouteService }
]
}).compileComponents();
versionService = TestBed.inject(VersionDataService);
versionHistoryService = TestBed.inject(VersionHistoryDataService);
});
beforeEach(() => {
fixture = TestBed.createComponent(VersionedItemComponent);
component = fixture.componentInstance;
component.object = mockItem;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
describe('when onCreateNewVersion() is called', () => {
it('should call versionService.findByHref', () => {
component.onCreateNewVersion();
expect(versionService.findByHref).toHaveBeenCalledWith('version-href');
});
});
});

View File

@@ -1,80 +0,0 @@
import { Component } from '@angular/core';
import { ItemComponent } from '../shared/item.component';
import { ItemVersionsSummaryModalComponent } from '../../../../shared/item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Version } from '../../../../core/shared/version.model';
import { switchMap, tap } from 'rxjs/operators';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
import { TranslateService } from '@ngx-translate/core';
import { VersionDataService } from '../../../../core/data/version-data.service';
import { ItemVersionsSharedService } from '../../../../shared/item/item-versions/item-versions-shared.service';
import { Router } from '@angular/router';
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
import { SearchService } from '../../../../core/shared/search/search.service';
import { Item } from '../../../../core/shared/item.model';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
import { RouteService } from '../../../../core/services/route.service';
@Component({
selector: 'ds-versioned-item',
templateUrl: './versioned-item.component.html',
styleUrls: ['./versioned-item.component.scss']
})
export class VersionedItemComponent extends ItemComponent {
constructor(
private modalService: NgbModal,
private versionHistoryService: VersionHistoryDataService,
private translateService: TranslateService,
private versionService: VersionDataService,
private itemVersionShared: ItemVersionsSharedService,
private router: Router,
private workspaceItemDataService: WorkspaceitemDataService,
private searchService: SearchService,
private itemService: ItemDataService,
protected routeService: RouteService
) {
super(routeService);
}
/**
* Open a modal that allows to create a new version starting from the specified item, with optional summary
*/
onCreateNewVersion(): void {
const item = this.object;
const versionHref = item._links.version.href;
// Open modal
const activeModal = this.modalService.open(ItemVersionsSummaryModalComponent);
// Show current version in modal
this.versionService.findByHref(versionHref).pipe(getFirstCompletedRemoteData()).subscribe((res: RemoteData<Version>) => {
// if res.hasNoContent then the item is unversioned
activeModal.componentInstance.firstVersion = res.hasNoContent;
activeModal.componentInstance.versionNumber = (res.hasNoContent ? undefined : res.payload.version);
});
// On createVersionEvent emitted create new version and notify
activeModal.componentInstance.createVersionEvent.pipe(
switchMap((summary: string) => this.versionHistoryService.createVersion(item._links.self.href, summary)),
getFirstCompletedRemoteData(),
// show success/failure notification
tap((res: RemoteData<Version>) => { this.itemVersionShared.notifyCreateNewVersion(res); }),
// get workspace item
getFirstSucceededRemoteDataPayload<Version>(),
switchMap((newVersion: Version) => this.itemService.findByHref(newVersion._links.item.href)),
getFirstSucceededRemoteDataPayload<Item>(),
switchMap((newVersionItem: Item) => this.workspaceItemDataService.findByItem(newVersionItem.uuid, true, false)),
getFirstSucceededRemoteDataPayload<WorkspaceItem>(),
).subscribe((wsItem) => {
const wsiId = wsItem.id;
const route = 'workspaceitems/' + wsiId + '/edit';
this.router.navigateByUrl(route);
});
}
}

View File

@@ -6,6 +6,7 @@
(mouseenter)="activateSection($event)"
(mouseleave)="deactivateSection($event)">
<a href="javascript:void(0);" class="nav-link dropdown-toggle" routerLinkActive="active"
[class.disabled]="section.model?.disabled"
id="browseDropdown" (click)="toggleSection($event)"
data-toggle="dropdown">
<ng-container

View File

@@ -0,0 +1,190 @@
import { TestBed, waitForAsync } from '@angular/core/testing';
import { MenuServiceStub } from '../testing/menu-service.stub';
import { of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { RouterTestingModule } from '@angular/router/testing';
import { AdminSidebarComponent } from '../../admin/admin-sidebar/admin-sidebar.component';
import { MenuService } from '../menu/menu.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { MenuID, MenuItemType } from '../menu/initial-menus-state';
import { DSOEditMenuResolver } from './dso-edit-menu.resolver';
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { Item } from '../../core/shared/item.model';
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
describe('DSOEditMenuResolver', () => {
const MENU_STATE = {
id: 'some menu'
};
let resolver: DSOEditMenuResolver;
let dSpaceObjectDataService;
let menuService;
let authorizationService;
let dsoVersioningModalService;
const route = {
data: {
menu: {
'statistics': [{
id: 'statistics-dummy-1',
active: false,
visible: true,
model: null
}]
}
},
params: {id: 'test-uuid'},
};
const state = {
url: 'test-url'
};
const testObject = Object.assign(new Item(), {uuid: 'test-uuid', type: 'item', _links: {self: {href: 'self-link'}}});
const dummySections1 = [{
id: 'dummy-1',
active: false,
visible: true,
model: null
},
{
id: 'dummy-2',
active: false,
visible: true,
model: null
}];
const dummySections2 = [{
id: 'dummy-3',
active: false,
visible: true,
model: null
},
{
id: 'dummy-4',
active: false,
visible: true,
model: null
},
{
id: 'dummy-5',
active: false,
visible: true,
model: null
}];
beforeEach(waitForAsync(() => {
menuService = new MenuServiceStub();
spyOn(menuService, 'getMenu').and.returnValue(observableOf(MENU_STATE));
dSpaceObjectDataService = jasmine.createSpyObj('dSpaceObjectDataService', {
findById: createSuccessfulRemoteDataObject$(testObject)
});
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
});
dsoVersioningModalService = jasmine.createSpyObj('dsoVersioningModalService', {
isNewVersionButtonDisabled: observableOf(false),
getVersioningTooltipMessage: observableOf('message'),
openCreateVersionModal: {}
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule],
declarations: [AdminSidebarComponent],
providers: [
{provide: DSpaceObjectDataService, useValue: dSpaceObjectDataService},
{provide: MenuService, useValue: menuService},
{provide: AuthorizationDataService, useValue: authorizationService},
{provide: DsoVersioningModalService, useValue: dsoVersioningModalService},
{
provide: NgbModal, useValue: {
open: () => {/*comment*/
}
}
}
],
schemas: [NO_ERRORS_SCHEMA]
});
resolver = TestBed.inject(DSOEditMenuResolver);
spyOn(menuService, 'addSection');
}));
it('should be created', () => {
expect(resolver).toBeTruthy();
});
describe('resolve', () => {
it('should create all menus when a dso is found', (done) => {
spyOn(resolver, 'getDsoMenus').and.returnValue(
[observableOf(dummySections1), observableOf(dummySections2)]
);
resolver.resolve(route as any, null).subscribe(resolved => {
expect(resolved).toEqual(
{
...route.data.menu,
[MenuID.DSO_EDIT]: [
...dummySections1.map((menu) => Object.assign(menu, {id: menu.id + '-test-uuid'})),
...dummySections2.map((menu) => Object.assign(menu, {id: menu.id + '-test-uuid'}))
]
}
);
expect(resolver.getDsoMenus).toHaveBeenCalled();
done();
});
});
it('should return the statistics menu when no dso is found', (done) => {
(dSpaceObjectDataService.findById as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
resolver.resolve(route as any, null).subscribe(resolved => {
expect(resolved).toEqual(
{
...route.data.menu
}
);
done();
});
});
});
describe('getDsoMenus', () => {
it('should return as first part the item version list ', (done) => {
const result = resolver.getDsoMenus(testObject, route, state);
result[0].subscribe((menuList) => {
expect(menuList.length).toEqual(1);
expect(menuList[0].id).toEqual('version-dso');
expect(menuList[0].active).toEqual(false);
expect(menuList[0].visible).toEqual(true);
expect(menuList[0].model.type).toEqual(MenuItemType.ONCLICK);
expect(menuList[0].model.text).toEqual('message');
expect(menuList[0].model.disabled).toEqual(false);
expect(menuList[0].icon).toEqual('code-branch');
done();
});
});
it('should return as second part the common list ', (done) => {
const result = resolver.getDsoMenus(testObject, route, state);
result[1].subscribe((menuList) => {
expect(menuList.length).toEqual(1);
expect(menuList[0].id).toEqual('edit-dso');
expect(menuList[0].active).toEqual(false);
expect(menuList[0].visible).toEqual(true);
expect(menuList[0].model.type).toEqual(MenuItemType.LINK);
expect(menuList[0].model.text).toEqual('item.page.edit');
expect(menuList[0].model.link).toEqual('test-url/edit/metadata');
expect(menuList[0].icon).toEqual('pencil-alt');
done();
});
});
});
});

View File

@@ -0,0 +1,164 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { combineLatest, Observable, of as observableOf } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { MenuID, MenuItemType } from '../menu/initial-menus-state';
import { MenuService } from '../menu/menu.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { Injectable } from '@angular/core';
import { LinkMenuItemModel } from '../menu/menu-item/models/link.model';
import { Item } from '../../core/shared/item.model';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { OnClickMenuItemModel } from '../menu/menu-item/models/onclick.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { map, switchMap } from 'rxjs/operators';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { URLCombiner } from '../../core/url-combiner/url-combiner';
import { DsoVersioningModalService } from './dso-versioning-modal-service/dso-versioning-modal.service';
import { hasValue } from '../empty.util';
import { MenuSection } from '../menu/menu.reducer';
/**
* Creates the menus for the dspace object pages
*/
@Injectable({
providedIn: 'root'
})
export class DSOEditMenuResolver implements Resolve<{ [key: string]: MenuSection[] }> {
constructor(
protected dSpaceObjectDataService: DSpaceObjectDataService,
protected menuService: MenuService,
protected authorizationService: AuthorizationDataService,
protected modalService: NgbModal,
protected dsoVersioningModalService: DsoVersioningModalService,
) {
}
/**
* Initialise all dspace object related menus
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{ [key: string]: MenuSection[] }> {
const uuid = route.params.id;
return this.dSpaceObjectDataService.findById(uuid, true, false).pipe(
getFirstCompletedRemoteData(),
switchMap((dsoRD) => {
if (dsoRD.hasSucceeded) {
const dso = dsoRD.payload;
return combineLatest(this.getDsoMenus(dso, route, state)).pipe(
map((combinedMenus) => [].concat.apply([], combinedMenus)),
map((menus) => this.addDsoUuidToMenuIDs(menus, dso)),
map((menus) => {
return {
...route.data?.menu,
[MenuID.DSO_EDIT]: menus
};
})
);
} else {
return observableOf({...route.data?.menu});
}
})
);
}
/**
* Return all the menus for a dso based on the route and state
*/
getDsoMenus(dso, route, state) {
return [
this.getItemMenu(dso),
this.getCommonMenu(dso, state)
];
}
/**
* Get the common menus between all dspace objects
*/
protected getCommonMenu(dso, state): Observable<any[]> {
return combineLatest([
this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, dso.self),
]).pipe(
map(([canEditItem]) => {
return [
{
id: 'edit-dso',
active: false,
visible: canEditItem,
model: {
type: MenuItemType.LINK,
text: this.getDsoType(dso) + '.page.edit',
link: new URLCombiner(state.url, 'edit', 'metadata').toString()
} as LinkMenuItemModel,
icon: 'pencil-alt',
index: 1
},
];
})
);
}
/**
* Get item sepcific menus
*/
protected getItemMenu(dso): Observable<any[]> {
if (dso instanceof Item) {
return combineLatest([
this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, dso.self),
this.dsoVersioningModalService.isNewVersionButtonDisabled(dso),
this.dsoVersioningModalService.getVersioningTooltipMessage(dso, 'item.page.version.hasDraft', 'item.page.version.create')
]).pipe(
map(([canCreateVersion, disableVersioning, versionTooltip]) => {
return [
{
id: 'version-dso',
active: false,
visible: canCreateVersion,
model: {
type: MenuItemType.ONCLICK,
text: versionTooltip,
disabled: disableVersioning,
function: () => {
this.dsoVersioningModalService.openCreateVersionModal(dso);
}
} as OnClickMenuItemModel,
icon: 'code-branch',
index: 0
},
];
}),
);
} else {
return observableOf([]);
}
}
/**
* Retrieve the dso or entity type for an object to be used in generic messages
*/
protected getDsoType(dso) {
const renderType = dso.getRenderTypes()[0];
if (typeof renderType === 'string' || renderType instanceof String) {
return renderType.toLowerCase();
} else {
return dso.type.toString().toLowerCase();
}
}
/**
* Add the dso uuid to all provided menu ids and parent ids
*/
protected addDsoUuidToMenuIDs(menus, dso) {
return menus.map((menu) => {
Object.assign(menu, {
id: menu.id + '-' + dso.uuid
});
if (hasValue(menu.parentID)) {
Object.assign(menu, {
parentID: menu.parentID + '-' + dso.uuid
});
}
return menu;
});
}
}

View File

@@ -0,0 +1,18 @@
<div class="dso-button-menu mb-1" ngbDropdown placement="bottom-right">
<div class="d-flex flex-row flex-nowrap"
[ngbTooltip]="itemModel.text | translate">
<button class="btn btn-dark btn-sm" ngbDropdownToggle [disabled]="section.model.disabled">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</button>
<ul ngbDropdownMenu>
<ng-container *ngFor="let subSection of (subSections$ | async)">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(subSection.id).component; injector: (sectionMap$ | async).get(subSection.id).injector;"></ng-container>
</ng-container>
</ul>
</div>
</div>

View File

@@ -0,0 +1,26 @@
.btn-dark {
background-color: var(--ds-admin-sidebar-bg);
}
.dso-button-menu {
.dropdown-toggle::after {
display: none;
}
}
ul.dropdown-menu {
background-color: var(--ds-admin-sidebar-bg);
color: white;
::ng-deep a {
color: white;
&.disabled {
color: $btn-link-disabled-color;
}
}
.disabled {
color: $btn-link-disabled-color;
}
}

View File

@@ -0,0 +1,75 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MenuServiceStub } from '../../../testing/menu-service.stub';
import { TranslateModule } from '@ngx-translate/core';
import { MenuService } from '../../../menu/menu.service';
import { CSSVariableService } from '../../../sass-helper/sass-helper.service';
import { CSSVariableServiceStub } from '../../../testing/css-variable-service.stub';
import { Router } from '@angular/router';
import { RouterStub } from '../../../testing/router.stub';
import { of as observableOf } from 'rxjs';
import { Component } from '@angular/core';
import { DsoEditMenuExpandableSectionComponent } from './dso-edit-menu-expandable-section.component';
import { MenuItemType } from '../../../menu/initial-menus-state';
import { By } from '@angular/platform-browser';
describe('DsoEditMenuExpandableSectionComponent', () => {
let component: DsoEditMenuExpandableSectionComponent;
let fixture: ComponentFixture<DsoEditMenuExpandableSectionComponent>;
const menuService = new MenuServiceStub();
const iconString = 'test';
const dummySection = {
id: 'dummy',
active: false,
visible: true,
model: {
type: MenuItemType.TEXT,
disabled: false,
text: 'text'
},
icon: iconString
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [DsoEditMenuExpandableSectionComponent, TestComponent],
providers: [
{provide: 'sectionDataProvider', useValue: dummySection},
{provide: MenuService, useValue: menuService},
{provide: CSSVariableService, useClass: CSSVariableServiceStub},
{provide: Router, useValue: new RouterStub()},
]
}).overrideComponent(DsoEditMenuExpandableSectionComponent, {
set: {
entryComponents: [TestComponent]
}
})
.compileComponents();
}));
beforeEach(() => {
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
fixture = TestBed.createComponent(DsoEditMenuExpandableSectionComponent);
component = fixture.componentInstance;
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should show a button with the icon', () => {
const button = fixture.debugElement.query(By.css('.btn-dark'));
expect(button.nativeElement.innerHTML).toContain('fa-' + iconString);
});
});
// declare a test component
@Component({
selector: 'ds-test-cmp',
template: ``
})
class TestComponent {
}

View File

@@ -0,0 +1,39 @@
import { Component, Inject, Injector } from '@angular/core';
import { MenuID } from 'src/app/shared/menu/initial-menus-state';
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
import { MenuSectionComponent } from 'src/app/shared/menu/menu-section/menu-section.component';
import { MenuService } from '../../../menu/menu.service';
import { MenuSection } from '../../../menu/menu.reducer';
import { Router } from '@angular/router';
/**
* Represents an expandable section in the dso edit menus
*/
@Component({
/* tslint:disable:component-selector */
selector: 'ds-dso-edit-menu-expandable-section',
templateUrl: './dso-edit-menu-expandable-section.component.html',
styleUrls: ['./dso-edit-menu-expandable-section.component.scss'],
})
@rendersSectionForMenu(MenuID.DSO_EDIT, true)
export class DsoEditMenuExpandableSectionComponent extends MenuSectionComponent {
menuID: MenuID = MenuID.DSO_EDIT;
itemModel;
constructor(
@Inject('sectionDataProvider') menuSection: MenuSection,
protected menuService: MenuService,
protected injector: Injector,
protected router: Router,
) {
super(menuSection, menuService, injector);
this.itemModel = menuSection.model;
}
ngOnInit(): void {
this.menuService.activateSection(this.menuID, this.section.id);
super.ngOnInit();
}
}

View File

@@ -0,0 +1,25 @@
<div *ngIf="!canActivate" class="dso-button-menu mb-1"
[title]="itemModel.text | translate"
[ngbTooltip]="itemModel.text | translate">
<a *ngIf="!section.model.disabled" class="d-flex flex-row flex-nowrap"
[routerLink]="itemModel.link"
href="javascript:void(0);">
<button class="btn btn-dark btn-sm" [disabled]="section.model.disabled">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</button>
</a>
<div *ngIf="section.model.disabled" class="d-flex flex-row flex-nowrap">
<button class="btn btn-dark btn-sm" [disabled]="section.model.disabled">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</button>
</div>
</div>
<div *ngIf="canActivate" class="dso-button-menu mb-1"
[title]="itemModel.text | translate"
[ngbTooltip]="itemModel.text | translate">
<button class="btn btn-dark btn-sm" [disabled]="section.model.disabled"
(click)="activate($event)">
<i class="fas fa-{{section.icon}} fa-fw"></i>
</button>
</div>

View File

@@ -0,0 +1,3 @@
.btn-dark {
background-color: var(--ds-admin-sidebar-bg);
}

View File

@@ -0,0 +1,173 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MenuServiceStub } from '../../../testing/menu-service.stub';
import { TranslateModule } from '@ngx-translate/core';
import { MenuService } from '../../../menu/menu.service';
import { CSSVariableService } from '../../../sass-helper/sass-helper.service';
import { CSSVariableServiceStub } from '../../../testing/css-variable-service.stub';
import { Router } from '@angular/router';
import { RouterStub } from '../../../testing/router.stub';
import { of as observableOf } from 'rxjs';
import { Component } from '@angular/core';
import { MenuItemType } from '../../../menu/initial-menus-state';
import { By } from '@angular/platform-browser';
import { DsoEditMenuSectionComponent } from './dso-edit-menu-section.component';
import { OnClickMenuItemModel } from '../../../menu/menu-item/models/onclick.model';
function initAsync(dummySectionText: { visible: boolean; icon: string; active: boolean; model: { disabled: boolean; text: string; type: MenuItemType }; id: string }, menuService: MenuServiceStub) {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [DsoEditMenuSectionComponent, TestComponent],
providers: [
{provide: 'sectionDataProvider', useValue: dummySectionText},
{provide: MenuService, useValue: menuService},
{provide: CSSVariableService, useClass: CSSVariableServiceStub},
{provide: Router, useValue: new RouterStub()},
]
}).overrideComponent(DsoEditMenuSectionComponent, {
set: {
entryComponents: [TestComponent]
}
})
.compileComponents();
}));
}
describe('DsoEditMenuSectionComponent', () => {
let component: DsoEditMenuSectionComponent;
let fixture: ComponentFixture<DsoEditMenuSectionComponent>;
const menuService = new MenuServiceStub();
const iconString = 'test';
const dummySectionText = {
id: 'dummy',
active: false,
visible: true,
model: {
type: MenuItemType.TEXT,
disabled: false,
text: 'text'
},
icon: iconString
};
const dummySectionLink = {
id: 'dummy',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
disabled: false,
text: 'text',
link: 'link'
},
icon: iconString
};
const dummySectionClick = {
id: 'dummy',
active: false,
visible: true,
model: {
type: MenuItemType.ONCLICK,
disabled: false,
text: 'text',
function: () => 'test'
},
icon: iconString
};
describe('text model', () => {
initAsync(dummySectionText, menuService);
beforeEach(() => {
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
fixture = TestBed.createComponent(DsoEditMenuSectionComponent);
component = fixture.componentInstance;
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should show a button with the icon', () => {
const button = fixture.debugElement.query(By.css('.btn-dark'));
expect(button.nativeElement.innerHTML).toContain('fa-' + iconString);
});
describe('when the section model in a disabled link or text', () => {
it('should show just the button', () => {
const textButton = fixture.debugElement.query(By.css('div div button'));
expect(textButton.nativeElement.innerHTML).toContain('fa-' + iconString);
});
});
});
describe('on click model', () => {
initAsync(dummySectionClick, menuService);
beforeEach(() => {
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
fixture = TestBed.createComponent(DsoEditMenuSectionComponent);
component = fixture.componentInstance;
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
fixture.detectChanges();
});
describe('when the section model in an on click menu', () => {
it('should call the activate method when clicking the button', () => {
spyOn(component, 'activate');
const button = fixture.debugElement.query(By.css('.btn-dark'));
button.triggerEventHandler('click', null);
expect(component.activate).toHaveBeenCalled();
});
});
describe('activate', () => {
const mockEvent = jasmine.createSpyObj('event', {
preventDefault: jasmine.createSpy('preventDefault'),
stopPropagation: jasmine.createSpy('stopPropagation'),
});
it('should call the item model function when not disabled', () => {
spyOn(component.section.model as OnClickMenuItemModel, 'function');
component.activate(mockEvent);
expect((component.section.model as OnClickMenuItemModel).function).toHaveBeenCalled();
});
it('should call not the item model function when disabled', () => {
spyOn(component.section.model as OnClickMenuItemModel, 'function');
component.itemModel.disabled = true;
component.activate(mockEvent);
expect((component.section.model as OnClickMenuItemModel).function).not.toHaveBeenCalled();
component.itemModel.disabled = false;
});
});
});
describe('link model', () => {
initAsync(dummySectionLink, menuService);
beforeEach(() => {
spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
fixture = TestBed.createComponent(DsoEditMenuSectionComponent);
component = fixture.componentInstance;
spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
fixture.detectChanges();
});
describe('when the section model in a non disabled link', () => {
it('should show a link element with the button in it', () => {
const link = fixture.debugElement.query(By.css('a'));
expect(link.nativeElement.innerHTML).toContain('button');
});
});
});
});
// declare a test component
@Component({
selector: 'ds-test-cmp',
template: ``
})
class TestComponent {
}

View File

@@ -0,0 +1,51 @@
import { Component, Inject, Injector, OnInit } from '@angular/core';
import { MenuID } from 'src/app/shared/menu/initial-menus-state';
import { rendersSectionForMenu } from 'src/app/shared/menu/menu-section.decorator';
import { MenuSectionComponent } from 'src/app/shared/menu/menu-section/menu-section.component';
import { MenuService } from '../../../menu/menu.service';
import { MenuSection } from '../../../menu/menu.reducer';
import { isNotEmpty } from '../../../empty.util';
/**
* Represents a non-expandable section in the dso edit menus
*/
@Component({
/* tslint:disable:component-selector */
selector: 'ds-dso-edit-menu-section',
templateUrl: './dso-edit-menu-section.component.html',
styleUrls: ['./dso-edit-menu-section.component.scss']
})
@rendersSectionForMenu(MenuID.DSO_EDIT, false)
export class DsoEditMenuSectionComponent extends MenuSectionComponent implements OnInit {
menuID: MenuID = MenuID.DSO_EDIT;
itemModel;
hasLink: boolean;
canActivate: boolean;
constructor(
@Inject('sectionDataProvider') menuSection: MenuSection,
protected menuService: MenuService,
protected injector: Injector,
) {
super(menuSection, menuService, injector);
this.itemModel = menuSection.model;
}
ngOnInit(): void {
this.hasLink = isNotEmpty(this.itemModel?.link);
this.canActivate = isNotEmpty(this.itemModel?.function);
super.ngOnInit();
}
/**
* Activate the section's model funtion
*/
public activate(event: any) {
event.preventDefault();
if (!this.itemModel.disabled) {
this.itemModel.function();
}
event.stopPropagation();
}
}

View File

@@ -0,0 +1,6 @@
<div class="dso-edit-menu d-flex flex-wrap">
<div *ngFor="let section of (sections | async)" class="ml-1">
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id)?.component; injector: (sectionMap$ | async).get(section.id)?.injector;"></ng-container>
</div>
</div>

View File

@@ -0,0 +1,78 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { DsoEditMenuComponent } from './dso-edit-menu.component';
import { MenuServiceStub } from '../../testing/menu-service.stub';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { AuthService } from '../../../core/auth/auth.service';
import { AuthServiceStub } from '../../testing/auth-service.stub';
import { MenuService } from '../../menu/menu.service';
import {
ExpandableNavbarSectionComponent
} from '../../../navbar/expandable-navbar-section/expandable-navbar-section.component';
import { MenuItemModel } from '../../menu/menu-item/models/menu-item.model';
describe('DsoEditMenuComponent', () => {
let comp: DsoEditMenuComponent;
let fixture: ComponentFixture<DsoEditMenuComponent>;
const menuService = new MenuServiceStub();
let authorizationService: AuthorizationDataService;
const routeStub = {
children: []
};
const section = {
id: 'edit-dso',
active: false,
visible: true,
model: {
type: null,
disabled: false,
} as MenuItemModel,
icon: 'pencil-alt',
index: 1
};
beforeEach(waitForAsync(() => {
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
});
spyOn(menuService, 'getMenuTopSections').and.returnValue(observableOf([section]));
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), RouterTestingModule],
declarations: [DsoEditMenuComponent],
providers: [
Injector,
{provide: MenuService, useValue: menuService},
{provide: AuthService, useClass: AuthServiceStub},
{provide: ActivatedRoute, useValue: routeStub},
{provide: AuthorizationDataService, useValue: authorizationService},
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ExpandableNavbarSectionComponent, {
set: {
changeDetection: ChangeDetectionStrategy.Default,
}
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsoEditMenuComponent);
comp = fixture.componentInstance;
comp.sections = observableOf([]);
fixture.detectChanges();
});
describe('onInit', () => {
it('should create', () => {
expect(comp).toBeTruthy();
});
});
});

View File

@@ -0,0 +1,36 @@
import { Component, Injector } from '@angular/core';
import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service';
import { MenuID } from '../../menu/initial-menus-state';
import { MenuComponent } from '../../menu/menu.component';
import { MenuService } from '../../menu/menu.service';
import { ActivatedRoute } from '@angular/router';
import { AuthService } from '../../../core/auth/auth.service';
/**
* Component representing the edit menu and other menus on the dspace object pages
*/
@Component({
selector: 'ds-dso-edit-menu',
styleUrls: ['./dso-edit-menu.component.scss'],
templateUrl: './dso-edit-menu.component.html',
})
export class DsoEditMenuComponent extends MenuComponent {
/**
* The menu ID of this component is DSO_EDIT
* @type {MenuID.DSO_EDIT}
*/
menuID = MenuID.DSO_EDIT;
constructor(protected menuService: MenuService,
protected injector: Injector,
public authorizationService: AuthorizationDataService,
public route: ActivatedRoute,
private authService: AuthService,
) {
super(menuService, injector, authorizationService, route);
}
ngOnInit(): void {
super.ngOnInit();
}
}

View File

@@ -1,7 +0,0 @@
<a *ngIf="isAuthorized$ | async"
[routerLink]="[pageRoute, 'edit', 'metadata']"
class="edit-button btn btn-dark btn-sm"
[ngbTooltip]="tooltipMsg | translate"
role="button" [title]="tooltipMsg |translate" [attr.aria-label]="tooltipMsg |translate">
<i class="fas fa-pencil-alt fa-fw"></i>
</a>

View File

@@ -1,3 +0,0 @@
.btn-dark {
background-color: var(--ds-admin-sidebar-bg);
}

View File

@@ -1,76 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DsoPageEditButtonComponent } from './dso-page-edit-button.component';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { Item } from '../../../core/shared/item.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
describe('DsoPageEditButtonComponent', () => {
let component: DsoPageEditButtonComponent;
let fixture: ComponentFixture<DsoPageEditButtonComponent>;
let authorizationService: AuthorizationDataService;
let dso: DSpaceObject;
beforeEach(waitForAsync(() => {
dso = Object.assign(new Item(), {
id: 'test-item',
_links: {
self: { href: 'test-item-selflink' }
}
});
authorizationService = jasmine.createSpyObj('authorizationService', {
isAuthorized: observableOf(true)
});
TestBed.configureTestingModule({
declarations: [DsoPageEditButtonComponent],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule],
providers: [
{ provide: AuthorizationDataService, useValue: authorizationService }
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsoPageEditButtonComponent);
component = fixture.componentInstance;
component.dso = dso;
component.pageRoute = 'test';
fixture.detectChanges();
});
it('should check the authorization of the current user', () => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanEditMetadata, dso.self);
});
describe('when the user is authorized', () => {
beforeEach(() => {
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(true));
component.ngOnInit();
fixture.detectChanges();
});
it('should render a link', () => {
const link = fixture.debugElement.query(By.css('a'));
expect(link).not.toBeNull();
});
});
describe('when the user is not authorized', () => {
beforeEach(() => {
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
component.ngOnInit();
fixture.detectChanges();
});
it('should not render a link', () => {
const link = fixture.debugElement.query(By.css('a'));
expect(link).toBeNull();
});
});
});

View File

@@ -1,43 +0,0 @@
import { Component, Input, OnInit } from '@angular/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { Observable } from 'rxjs';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
@Component({
selector: 'ds-dso-page-edit-button',
templateUrl: './dso-page-edit-button.component.html',
styleUrls: ['./dso-page-edit-button.component.scss']
})
/**
* Display a button linking to the edit page of a DSpaceObject
*/
export class DsoPageEditButtonComponent implements OnInit {
/**
* The DSpaceObject to display a button to the edit page for
*/
@Input() dso: DSpaceObject;
/**
* The prefix of the route to the edit page (before the object's UUID, e.g. "items")
*/
@Input() pageRoute: string;
/**
* A message for the tooltip on the button
* Supports i18n keys
*/
@Input() tooltipMsg: string;
/**
* Whether or not the current user is authorized to edit the DSpaceObject
*/
isAuthorized$: Observable<boolean>;
constructor(protected authorizationService: AuthorizationDataService) { }
ngOnInit() {
this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanEditMetadata, this.dso.self);
}
}

View File

@@ -1,8 +0,0 @@
<button *ngIf="isAuthorized$ | async"
class="edit-button btn btn-dark btn-sm"
(click)="createNewVersion()"
[disabled]="disableNewVersionButton$ | async"
[ngbTooltip]="tooltipMsg$ | async | translate"
role="button" [title]="tooltipMsg$ | async |translate" [attr.aria-label]="tooltipMsg$ | async | translate">
<i class="fas fa-code-branch fa-fw"></i>
</button>

View File

@@ -1,3 +0,0 @@
.btn-dark {
background-color: var(--ds-admin-sidebar-bg);
}

View File

@@ -1,96 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { DsoPageVersionButtonComponent } from './dso-page-version-button.component';
import { Item } from '../../../core/shared/item.model';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { Observable, of, of as observableOf } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
describe('DsoPageVersionButtonComponent', () => {
let component: DsoPageVersionButtonComponent;
let fixture: ComponentFixture<DsoPageVersionButtonComponent>;
let authorizationService: AuthorizationDataService;
let versionHistoryService: VersionHistoryDataService;
let dso: Item;
let tooltipMsg: Observable<string>;
const authorizationServiceSpy = jasmine.createSpyObj('authorizationService', ['isAuthorized']);
const versionHistoryServiceSpy = jasmine.createSpyObj('versionHistoryService',
['getVersions', 'getLatestVersionFromHistory$', 'isLatest$', 'hasDraftVersion$']
);
beforeEach(waitForAsync(() => {
dso = Object.assign(new Item(), {
id: 'test-item',
_links: {
self: { href: 'test-item-selflink' },
version: { href: 'test-item-version-selflink' },
},
});
tooltipMsg = of('tooltip-msg');
TestBed.configureTestingModule({
declarations: [DsoPageVersionButtonComponent],
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([]), NgbModule],
providers: [
{ provide: AuthorizationDataService, useValue: authorizationServiceSpy },
{ provide: VersionHistoryDataService, useValue: versionHistoryServiceSpy },
]
}).compileComponents();
authorizationService = TestBed.inject(AuthorizationDataService);
versionHistoryService = TestBed.inject(VersionHistoryDataService);
versionHistoryServiceSpy.hasDraftVersion$.and.returnValue(observableOf(true));
}));
beforeEach(() => {
fixture = TestBed.createComponent(DsoPageVersionButtonComponent);
component = fixture.componentInstance;
component.dso = dso;
component.tooltipMsg$ = tooltipMsg;
fixture.detectChanges();
});
it('should check the authorization of the current user', () => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(FeatureID.CanCreateVersion, dso.self);
});
it('should check if the item has a draft version', () => {
expect(versionHistoryServiceSpy.hasDraftVersion$).toHaveBeenCalledWith(dso._links.version.href);
});
describe('when the user is authorized', () => {
beforeEach(() => {
authorizationServiceSpy.isAuthorized.and.returnValue(observableOf(true));
component.ngOnInit();
fixture.detectChanges();
});
it('should render a button', () => {
const button = fixture.debugElement.query(By.css('button'));
expect(button).not.toBeNull();
});
});
describe('when the user is not authorized', () => {
beforeEach(() => {
authorizationServiceSpy.isAuthorized.and.returnValue(observableOf(false));
component.ngOnInit();
fixture.detectChanges();
});
it('should render a button', () => {
const button = fixture.debugElement.query(By.css('button'));
expect(button).toBeNull();
});
});
});

View File

@@ -1,77 +0,0 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
import { Observable, of } from 'rxjs';
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { Item } from '../../../core/shared/item.model';
import { map, startWith, switchMap } from 'rxjs/operators';
@Component({
selector: 'ds-dso-page-version-button',
templateUrl: './dso-page-version-button.component.html',
styleUrls: ['./dso-page-version-button.component.scss']
})
/**
* Display a button linking to the edit page of a DSpaceObject
*/
export class DsoPageVersionButtonComponent implements OnInit {
/**
* The item for which display a button to create a new version
*/
@Input() dso: Item;
/**
* A message for the tooltip on the button
* Supports i18n keys
*/
@Input() tooltipMsgCreate: string;
/**
* A message for the tooltip on the button (when is disabled)
* Supports i18n keys
*/
@Input() tooltipMsgHasDraft: string;
/**
* Emits an event that triggers the creation of the new version
*/
@Output() newVersionEvent = new EventEmitter();
/**
* Whether or not the current user is authorized to create a new version of the DSpaceObject
*/
isAuthorized$: Observable<boolean>;
disableNewVersionButton$: Observable<boolean>;
tooltipMsg$: Observable<string>;
constructor(
protected authorizationService: AuthorizationDataService,
protected versionHistoryService: VersionHistoryDataService,
) {
}
/**
* Creates a new version for the current item
*/
createNewVersion() {
this.newVersionEvent.emit();
}
ngOnInit() {
this.isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.dso.self);
this.disableNewVersionButton$ = this.versionHistoryService.hasDraftVersion$(this.dso._links.version.href).pipe(
// button is disabled if hasDraftVersion = true, and enabled if hasDraftVersion = false or null
// (hasDraftVersion is null when a version history does not exist)
map((res) => Boolean(res)),
startWith(true),
);
this.tooltipMsg$ = this.disableNewVersionButton$.pipe(
switchMap((hasDraftVersion) => of(hasDraftVersion ? this.tooltipMsgHasDraft : this.tooltipMsgCreate)),
);
}
}

View File

@@ -0,0 +1,92 @@
import { DsoVersioningModalService } from './dso-versioning-modal.service';
import { waitForAsync } from '@angular/core/testing';
import { createSuccessfulRemoteDataObject$ } from '../../remote-data.utils';
import { Version } from '../../../core/shared/version.model';
import { Item } from '../../../core/shared/item.model';
import { MetadataMap } from '../../../core/shared/metadata.models';
import { createRelationshipsObservable } from '../../../item-page/simple/item-types/shared/item.component.spec';
import { buildPaginatedList } from '../../../core/data/paginated-list.model';
import { PageInfo } from '../../../core/shared/page-info.model';
import { EMPTY, of as observableOf } from 'rxjs';
fdescribe('DsoVersioningModalService', () => {
let service: DsoVersioningModalService;
let modalService;
let versionService;
let versionHistoryService;
let itemVersionShared;
let router;
let workspaceItemDataService;
let itemService;
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [])),
metadata: new MetadataMap(),
relationships: createRelationshipsObservable(),
_links: {
self: {
href: 'item-href'
},
version: {
href: 'version-href'
}
}
});
beforeEach(waitForAsync(() => {
modalService = jasmine.createSpyObj('modalService', {
open: {componentInstance: {firstVersion: {}, versionNumber: {}, createVersionEvent: EMPTY}}
});
versionService = jasmine.createSpyObj('versionService', {
findByHref: createSuccessfulRemoteDataObject$<Version>(new Version()),
});
versionHistoryService = jasmine.createSpyObj('versionHistoryService', {
createVersion: createSuccessfulRemoteDataObject$<Version>(new Version()),
hasDraftVersion$: observableOf(false)
});
itemVersionShared = jasmine.createSpyObj('itemVersionShared', ['notifyCreateNewVersion']);
router = jasmine.createSpyObj('router', ['navigateByUrl']);
workspaceItemDataService = jasmine.createSpyObj('workspaceItemDataService', ['findByItem']);
itemService = jasmine.createSpyObj('itemService', ['findByHref']);
service = new DsoVersioningModalService(
modalService,
versionService,
versionHistoryService,
itemVersionShared,
router,
workspaceItemDataService,
itemService
);
}));
describe('when onCreateNewVersion() is called', () => {
it('should call versionService.findByHref', () => {
service.openCreateVersionModal(mockItem);
expect(versionService.findByHref).toHaveBeenCalledWith('version-href');
});
});
describe('isNewVersionButtonDisabled', () => {
it('should call versionHistoryService.hasDraftVersion$', () => {
service.isNewVersionButtonDisabled(mockItem);
expect(versionHistoryService.hasDraftVersion$).toHaveBeenCalledWith(mockItem._links.version.href);
});
});
describe('getVersioningTooltipMessage', () => {
it('should return the create message when isNewVersionButtonDisabled returns false', (done) => {
spyOn(service, 'isNewVersionButtonDisabled').and.returnValue(observableOf(false));
service.getVersioningTooltipMessage(mockItem, 'draft-message', 'create-message').subscribe((message) => {
expect(message).toEqual('create-message');
done();
});
});
it('should return the draft message when isNewVersionButtonDisabled returns true', (done) => {
spyOn(service, 'isNewVersionButtonDisabled').and.returnValue(observableOf(true));
service.getVersioningTooltipMessage(mockItem, 'draft-message', 'create-message').subscribe((message) => {
expect(message).toEqual('draft-message');
done();
});
});
});
});

View File

@@ -0,0 +1,98 @@
import {
ItemVersionsSummaryModalComponent
} from '../../item/item-versions/item-versions-summary-modal/item-versions-summary-modal.component';
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data';
import { Version } from '../../../core/shared/version.model';
import { map, startWith, switchMap, tap } from 'rxjs/operators';
import { Item } from '../../../core/shared/item.model';
import { WorkspaceItem } from '../../../core/submission/models/workspaceitem.model';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { VersionDataService } from '../../../core/data/version-data.service';
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
import { ItemVersionsSharedService } from '../../item/item-versions/item-versions-shared.service';
import { Router } from '@angular/router';
import { WorkspaceitemDataService } from '../../../core/submission/workspaceitem-data.service';
import { ItemDataService } from '../../../core/data/item-data.service';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
/**
* Service to take care of all the functionality related to the version creation modal
*/
@Injectable({
providedIn: 'root'
})
export class DsoVersioningModalService {
constructor(
protected modalService: NgbModal,
protected versionService: VersionDataService,
protected versionHistoryService: VersionHistoryDataService,
protected itemVersionShared: ItemVersionsSharedService,
protected router: Router,
protected workspaceItemDataService: WorkspaceitemDataService,
protected itemService: ItemDataService,
) {
}
/**
* Open the create version modal for the provided dso
*/
openCreateVersionModal(dso): void {
const item = dso;
const versionHref = item._links.version.href;
// Open modal
const activeModal = this.modalService.open(ItemVersionsSummaryModalComponent);
// Show current version in modal
this.versionService.findByHref(versionHref).pipe(getFirstCompletedRemoteData()).subscribe((res: RemoteData<Version>) => {
// if res.hasNoContent then the item is unversioned
activeModal.componentInstance.firstVersion = res.hasNoContent;
activeModal.componentInstance.versionNumber = (res.hasNoContent ? undefined : res.payload.version);
});
// On createVersionEvent emitted create new version and notify
activeModal.componentInstance.createVersionEvent.pipe(
switchMap((summary: string) => this.versionHistoryService.createVersion(item._links.self.href, summary)),
getFirstCompletedRemoteData(),
// show success/failure notification
tap((res: RemoteData<Version>) => {
this.itemVersionShared.notifyCreateNewVersion(res);
}),
// get workspace item
getFirstSucceededRemoteDataPayload<Version>(),
switchMap((newVersion: Version) => this.itemService.findByHref(newVersion._links.item.href)),
getFirstSucceededRemoteDataPayload<Item>(),
switchMap((newVersionItem: Item) => this.workspaceItemDataService.findByItem(newVersionItem.uuid, true, false)),
getFirstSucceededRemoteDataPayload<WorkspaceItem>(),
).subscribe((wsItem) => {
const wsiId = wsItem.id;
const route = 'workspaceitems/' + wsiId + '/edit';
this.router.navigateByUrl(route);
});
}
/**
* Checks if the new version button should be disabled for the provided dso
*/
isNewVersionButtonDisabled(dso): Observable<boolean> {
return this.versionHistoryService.hasDraftVersion$(dso._links.version.href).pipe(
// button is disabled if hasDraftVersion = true, and enabled if hasDraftVersion = false or null
// (hasDraftVersion is null when a version history does not exist)
map((res) => Boolean(res)),
startWith(true),
);
}
/**
* Checks and returns the tooltip that needs to be used for the create version button tooltip
*/
getVersioningTooltipMessage(dso, tooltipMsgHasDraft, tooltipMsgCreate): Observable<string> {
return this.isNewVersionButtonDisabled(dso).pipe(
switchMap((hasDraftVersion) => of(hasDraftVersion ? tooltipMsgHasDraft : tooltipMsgCreate)),
);
}
}

View File

@@ -5,7 +5,8 @@ import { MenusState } from './menu.reducer';
*/
export enum MenuID {
ADMIN = 'admin-sidebar',
PUBLIC = 'public'
PUBLIC = 'public',
DSO_EDIT = 'dso-edit'
}
/**
@@ -36,5 +37,14 @@ export const initialMenusState: MenusState = {
visible: true,
sections: {},
sectionToSubsectionIndex: {}
}
},
[MenuID.DSO_EDIT]:
{
id: MenuID.DSO_EDIT,
collapsed: true,
previewCollapsed: true,
visible: false,
sections: {},
sectionToSubsectionIndex: {}
},
};

View File

@@ -1,6 +1,6 @@
<a class="nav-item nav-link"
[ngClass]="{ 'disabled': !hasLink }"
[attr.aria-disabled]="!hasLink"
[ngClass]="{ 'disabled': !hasLink || item.disabled }"
[attr.aria-disabled]="!hasLink || item.disabled"
[title]="item.text | translate"
[routerLink]="getRouterLink()"
(click)="$event.stopPropagation()"

View File

@@ -37,7 +37,7 @@ export class LinkMenuItemComponent implements OnInit {
navigate(event: any) {
event.preventDefault();
if (this.getRouterLink()) {
if (!this.item.disabled && this.getRouterLink()) {
this.router.navigate([this.getRouterLink()]);
}
event.stopPropagation();

View File

@@ -6,5 +6,6 @@ import { MenuItemModel } from './menu-item.model';
*/
export class AltmetricMenuItemModel implements MenuItemModel {
type = MenuItemType.ALTMETRIC;
disabled: boolean;
url: string;
}

View File

@@ -6,6 +6,7 @@ import { MenuItemType } from '../../initial-menus-state';
*/
export class LinkMenuItemModel implements MenuItemModel {
type = MenuItemType.LINK;
disabled: boolean;
text: string;
link: string;
}

View File

@@ -5,4 +5,5 @@ import { MenuItemType } from '../../initial-menus-state';
*/
export interface MenuItemModel {
type: MenuItemType;
disabled: boolean;
}

View File

@@ -6,6 +6,7 @@ import { MenuItemType } from '../../initial-menus-state';
*/
export class OnClickMenuItemModel implements MenuItemModel {
type = MenuItemType.ONCLICK;
disabled: boolean;
text: string;
function: () => {};
}

View File

@@ -6,6 +6,7 @@ import { MenuItemModel } from './menu-item.model';
*/
export class SearchMenuItemModel implements MenuItemModel {
type = MenuItemType.SEARCH;
disabled: boolean;
placeholder: string;
action: string;
}

View File

@@ -6,5 +6,6 @@ import { MenuItemModel } from './menu-item.model';
*/
export class TextMenuItemModel implements MenuItemModel {
type = MenuItemType.TEXT;
disabled: boolean;
text: string;
}

View File

@@ -1,4 +1,5 @@
<a href="javascript:void(0);"
<a *ngIf="!item.disabled"
href="javascript:void(0);"
class="nav-item nav-link"
role="button"
[title]="item.text | translate"
@@ -6,3 +7,4 @@
(keyup.space)="activate($event)"
(keyup.enter)="activate($event)"
>{{item.text | translate}}</a>
<span *ngIf="item.disabled" class="nav-item nav-link disabled">{{item.text | translate}}</span>

View File

@@ -14,13 +14,16 @@ import { OnClickMenuItemModel } from './models/onclick.model';
@rendersMenuItemForType(MenuItemType.ONCLICK)
export class OnClickMenuItemComponent {
item: OnClickMenuItemModel;
constructor(@Inject('itemModelProvider') item: OnClickMenuItemModel) {
this.item = item;
}
public activate(event: any) {
if (!this.item.disabled) {
event.preventDefault();
this.item.function();
event.stopPropagation();
}
}
}

View File

@@ -1 +1 @@
<span>{{item.text | translate}}</span>
<span [class.disabled]="item.disabled">{{item.text | translate}}</span>

View File

@@ -64,8 +64,10 @@ export class MenuSectionComponent implements OnInit, OnDestroy {
*/
toggleSection(event: Event) {
event.preventDefault();
if (!this.section.model?.disabled) {
this.menuService.toggleActiveSection(this.menuID, this.section.id);
}
}
/**
* Activate this section
@@ -73,8 +75,10 @@ export class MenuSectionComponent implements OnInit, OnDestroy {
*/
activateSection(event: Event) {
event.preventDefault();
if (!this.section.model?.disabled) {
this.menuService.activateSection(this.menuID, this.section.id);
}
}
/**
* Deactivate this section

View File

@@ -157,8 +157,6 @@ import { SidebarSearchListElementComponent } from './object-list/sidebar-search-
import { CollectionSidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/collection/collection-sidebar-search-list-element.component';
import { CommunitySidebarSearchListElementComponent } from './object-list/sidebar-search-list-element/community/community-sidebar-search-list-element.component';
import { AuthorizedCollectionSelectorComponent } from './dso-selector/dso-selector/authorized-collection-selector/authorized-collection-selector.component';
import { DsoPageEditButtonComponent } from './dso-page/dso-page-edit-button/dso-page-edit-button.component';
import { DsoPageVersionButtonComponent } from './dso-page/dso-page-version-button/dso-page-version-button.component';
import { HoverClassDirective } from './hover-class.directive';
import { ValidationSuggestionsComponent } from './input-suggestions/validation-suggestions/validation-suggestions.component';
import { ItemAlertsComponent } from './item/item-alerts/item-alerts.component';
@@ -177,6 +175,13 @@ import { ScopeSelectorModalComponent } from './search-form/scope-selector-modal/
import { BitstreamRequestACopyPageComponent } from './bitstream-request-a-copy-page/bitstream-request-a-copy-page.component';
import { DsSelectComponent } from './ds-select/ds-select.component';
import { LogInOidcComponent } from './log-in/methods/oidc/log-in-oidc.component';
import {
DsoEditMenuSectionComponent
} from './dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component';
import { DsoEditMenuComponent } from './dso-page/dso-edit-menu/dso-edit-menu.component';
import {
DsoEditMenuExpandableSectionComponent
} from './dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -402,18 +407,20 @@ const ENTRY_COMPONENTS = [
OnClickMenuItemComponent,
TextMenuItemComponent,
ScopeSelectorModalComponent,
DsoEditMenuSectionComponent,
DsoEditMenuExpandableSectionComponent,
];
const SHARED_ITEM_PAGE_COMPONENTS = [
MetadataFieldWrapperComponent,
MetadataValuesComponent,
DsoPageEditButtonComponent,
DsoPageVersionButtonComponent,
ItemAlertsComponent,
GenericItemPageFieldComponent,
MetadataRepresentationListComponent,
RelatedItemsComponent,
DsoEditMenuSectionComponent,
DsoEditMenuComponent,
DsoEditMenuExpandableSectionComponent,
];
const PROVIDERS = [