From 0000da2c31d4d1c2b572873c120c2bf6147a327b Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 3 Aug 2026 10:32:47 +0200 Subject: [PATCH 1/4] [DURACOM-501] implement dso public menu --- src/app/app.menus.ts | 3 + ...lic-menu-expandable-section.component.html | 28 +++ ...lic-menu-expandable-section.component.scss | 41 ++++ ...-menu-expandable-section.component.spec.ts | 111 +++++++++++ ...ublic-menu-expandable-section.component.ts | 99 ++++++++++ .../dso-public-menu-section.component.html | 29 +++ .../dso-public-menu-section.component.scss | 3 + .../dso-public-menu-section.component.spec.ts | 184 ++++++++++++++++++ .../dso-public-menu-section.component.ts | 71 +++++++ .../dso-public-menu.component.html | 11 ++ .../dso-public-menu.component.scss | 0 .../dso-public-menu.component.spec.ts | 119 +++++++++++ .../dso-public-menu.component.ts | 75 +++++++ src/app/shared/menu/initial-menus-state.ts | 9 + src/app/shared/menu/menu-id.model.ts | 3 +- .../shared/menu/menu-provider.service.spec.ts | 6 + src/app/shared/menu/menu-section.decorator.ts | 7 + src/app/shared/menu/menu.structure.spec.ts | 2 + 18 files changed, 800 insertions(+), 1 deletion(-) create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.html create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.scss create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.spec.ts create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.ts create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.html create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.scss create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.spec.ts create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.ts create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.html create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.scss create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.spec.ts create mode 100644 src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.ts diff --git a/src/app/app.menus.ts b/src/app/app.menus.ts index 73190c57caf..23df43a7d27 100644 --- a/src/app/app.menus.ts +++ b/src/app/app.menus.ts @@ -50,6 +50,7 @@ import { WorkflowMenuProvider } from './shared/menu/providers/workflow.menu'; * - `MenuID.PUBLIC`: Defines menus accessible by the public in the navigation bar. * - `MenuID.ADMIN`: Defines menus for administrative users in the sidebar. * - `MenuID.DSO_EDIT`: Defines dynamic menu options for DSpace Objects that will be present on the DSpace Object's page. + * - `MenuID.DSO_PUBLIC`: Defines dynamic menu options for DSpace Objects available to unauthenticated users. * * To add more menu sections to a menu (public navbar, admin sidebar or the dso edit menus), * a new provider can be added to the list with the corresponding menu ID. @@ -118,4 +119,6 @@ export const MENUS = buildMenuStructure({ ), ]), ], + [MenuID.DSO_PUBLIC]: [ + ], }); diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.html b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.html new file mode 100644 index 00000000000..2ec01cde6d8 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.html @@ -0,0 +1,28 @@ +@if (hasSubSections$ | async) { +
+
+ + +
+
+} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.scss b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.scss new file mode 100644 index 00000000000..106ccff11a2 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.scss @@ -0,0 +1,41 @@ +.btn-dark { + background-color: var(--ds-admin-sidebar-bg); +} + +.dso-button-menu { + .dropdown-toggle::after { + content: ''; + width: 0; + height: 0; + border-style: solid; + border-width: 12px 12px 0 0; + border-color: transparent #627a91 transparent transparent; + border-bottom-right-radius: var(--bs-btn-border-radius-sm); + right: 0; + bottom: 0; + position: absolute; + overflow: hidden; + } + overflow: hidden; +} + +ul.dropdown-menu { + background-color: var(--ds-admin-sidebar-bg); + color: white; + + ::ng-deep a { + color: white; + + &.disabled { + color: var(--bs-btn-link-disabled-color); + } + } + + .disabled { + color: var(--bs-btn-link-disabled-color); + } +} + +.dso-public-menu-dropdown { + max-width: calc(min(600px, 75vw)); +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.spec.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.spec.ts new file mode 100644 index 00000000000..6ff9ed01408 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.spec.ts @@ -0,0 +1,111 @@ +import { Component } from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Router } from '@angular/router'; +import { CSSVariableServiceStub } from '@dspace/core/testing/css-variable-service.stub'; +import { RouterStub } from '@dspace/core/testing/router.stub'; +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; + +import { MenuService } from '../../../menu/menu.service'; +import { MenuItemType } from '../../../menu/menu-item-type.model'; +import { MenuItemModels } from '../../../menu/menu-section.model'; +import { MenuServiceStub } from '../../../menu/menu-service.stub'; +import { CSSVariableService } from '../../../sass-helper/css-variable.service'; +import { DsoPublicMenuExpandableSectionComponent } from './dso-public-menu-expandable-section.component'; + +describe('DsoPublicMenuExpandableSectionComponent', () => { + let component: DsoPublicMenuExpandableSectionComponent; + let fixture: ComponentFixture; + 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, + }; + + describe('when there are subsections', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), DsoPublicMenuExpandableSectionComponent, TestComponent], + providers: [ + { provide: 'sectionDataProvider', useValue: dummySection }, + { provide: MenuService, useValue: menuService }, + { provide: CSSVariableService, useClass: CSSVariableServiceStub }, + { provide: Router, useValue: new RouterStub() }, + ], + }).compileComponents(); + })); + + beforeEach(() => { + spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([{ + id: 'test', + visible: true, + model: {} as MenuItemModels, + }])); + fixture = TestBed.createComponent(DsoPublicMenuExpandableSectionComponent); + 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 there are no subsections', () => { + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), DsoPublicMenuExpandableSectionComponent, TestComponent], + providers: [ + { provide: 'sectionDataProvider', useValue: dummySection }, + { provide: MenuService, useValue: menuService }, + { provide: CSSVariableService, useClass: CSSVariableServiceStub }, + { provide: Router, useValue: new RouterStub() }, + ], + }).compileComponents(); + })); + + beforeEach(() => { + spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([])); + fixture = TestBed.createComponent(DsoPublicMenuExpandableSectionComponent); + component = fixture.componentInstance; + spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should now show a button', () => { + const button = fixture.debugElement.query(By.css('.btn-dark')); + expect(button).toBeNull(); + }); + }); +}); + +@Component({ + selector: 'ds-test-cmp', + template: ``, +}) +class TestComponent { +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.ts new file mode 100644 index 00000000000..3757d9b9473 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component.ts @@ -0,0 +1,99 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { + AsyncPipe, + NgComponentOutlet, +} from '@angular/common'; +import { + Component, + Inject, + Injector, + OnInit, +} from '@angular/core'; +import { Router } from '@angular/router'; +import { + hasValue, + isNotEmpty, +} from '@dspace/shared/utils/empty.util'; +import { + NgbDropdownModule, + NgbTooltip, +} from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { MenuID } from 'src/app/shared/menu/menu-id.model'; +import { MenuSection } from 'src/app/shared/menu/menu-section.model'; +import { AbstractMenuSectionComponent } from 'src/app/shared/menu/menu-section/abstract-menu-section.component'; + +import { BtnDisabledDirective } from '../../../btn-disabled.directive'; +import { MenuService } from '../../../menu/menu.service'; + +/** + * Represents an expandable section in the dso public menus + */ +@Component({ + selector: 'ds-dso-public-menu-expandable-section', + templateUrl: './dso-public-menu-expandable-section.component.html', + styleUrls: ['./dso-public-menu-expandable-section.component.scss'], + imports: [ + AsyncPipe, + BtnDisabledDirective, + NgbDropdownModule, + NgbTooltip, + NgComponentOutlet, + TranslateModule, + ], +}) +export class DsoPublicMenuExpandableSectionComponent extends AbstractMenuSectionComponent implements OnInit { + + /** + * This section resides in the DSO public menu + */ + menuID: MenuID = MenuID.DSO_PUBLIC; + + /** + * The MenuItemModel of the top section + */ + itemModel; + + /** + * Emits whether one of the subsections contains an icon + */ + renderIcons$: Observable; + + /** + * Emits true when the top section has subsections, else emits false + */ + hasSubSections$: Observable; + + constructor( + @Inject('sectionDataProvider') protected section: MenuSection, + protected menuService: MenuService, + protected injector: Injector, + protected router: Router, + ) { + super(menuService, injector); + this.itemModel = section.model; + } + + ngOnInit(): void { + this.menuService.activateSection(this.menuID, this.section.id); + super.ngOnInit(); + + this.renderIcons$ = this.subSections$.pipe( + map((sections: MenuSection[]) => { + return sections.some(section => hasValue(section.icon)); + }), + ); + + this.hasSubSections$ = this.subSections$.pipe( + map((subSections) => isNotEmpty(subSections)), + ); + } +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.html b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.html new file mode 100644 index 00000000000..b2c8ca84ce9 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.html @@ -0,0 +1,29 @@ +@if (!canActivate) { +
+ @if (!section.model.disabled) { + + + {{itemModel.text | translate}} + + } + @if (section.model.disabled) { + + } +
+} + +@if (canActivate) { +
+ +
+} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.scss b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.scss new file mode 100644 index 00000000000..cf0e81c5538 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.scss @@ -0,0 +1,3 @@ +.btn-dark { + background-color: var(--ds-admin-sidebar-bg); +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.spec.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.spec.ts new file mode 100644 index 00000000000..31322e1836c --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.spec.ts @@ -0,0 +1,184 @@ +import { Component } from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { + ActivatedRoute, + Router, +} from '@angular/router'; +import { ActivatedRouteStub } from '@dspace/core/testing/active-router.stub'; +import { CSSVariableServiceStub } from '@dspace/core/testing/css-variable-service.stub'; +import { RouterStub } from '@dspace/core/testing/router.stub'; +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; +import { MenuItemType } from 'src/app/shared/menu/menu-item-type.model'; + +import { MenuService } from '../../../menu/menu.service'; +import { OnClickMenuItemModel } from '../../../menu/menu-item/models/onclick.model'; +import { MenuServiceStub } from '../../../menu/menu-service.stub'; +import { CSSVariableService } from '../../../sass-helper/css-variable.service'; +import { DsoPublicMenuSectionComponent } from './dso-public-menu-section.component'; + +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(), + DsoPublicMenuSectionComponent, + TestComponent, + ], + providers: [ + { provide: 'sectionDataProvider', useValue: dummySectionText }, + { provide: MenuService, useValue: menuService }, + { provide: CSSVariableService, useClass: CSSVariableServiceStub }, + { provide: Router, useValue: new RouterStub() }, + { provide: ActivatedRoute, useValue: new ActivatedRouteStub() }, + ], + }).compileComponents(); + })); +} + +describe('DsoPublicMenuSectionComponent', () => { + let component: DsoPublicMenuSectionComponent; + let fixture: ComponentFixture; + 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(of([])); + fixture = TestBed.createComponent(DsoPublicMenuSectionComponent); + 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 a')); + expect(textButton.nativeElement.innerHTML).toContain('fa-' + iconString); + }); + }); + }); + describe('on click model', () => { + initAsync(dummySectionClick, menuService); + beforeEach(() => { + spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([])); + fixture = TestBed.createComponent(DsoPublicMenuSectionComponent); + 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 as any).section.model as OnClickMenuItemModel, 'function'); + component.activate(mockEvent); + + expect(((component as any).section.model as OnClickMenuItemModel).function).toHaveBeenCalled(); + }); + it('should call not the item model function when disabled', () => { + spyOn((component as any).section.model as OnClickMenuItemModel, 'function'); + component.itemModel.disabled = true; + component.activate(mockEvent); + + expect(((component as any).section.model as OnClickMenuItemModel).function).not.toHaveBeenCalled(); + component.itemModel.disabled = false; + }); + }); + + }); + + describe('when the section model in a non disabled link', () => { + initAsync(dummySectionLink, menuService); + beforeEach(() => { + spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([])); + fixture = TestBed.createComponent(DsoPublicMenuSectionComponent); + component = fixture.componentInstance; + spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent); + fixture.detectChanges(); + }); + + it('should show the link element', () => { + expect(fixture.debugElement.query(By.css('a'))).not.toBeNull(); + }); + + }); +}); + +@Component({ + selector: 'ds-test-cmp', + template: ``, +}) +class TestComponent { +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.ts new file mode 100644 index 00000000000..f13852583bf --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component.ts @@ -0,0 +1,71 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { + Component, + Inject, + Injector, + OnInit, +} from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { isNotEmpty } from '@dspace/shared/utils/empty.util'; +import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateModule } from '@ngx-translate/core'; +import { AbstractMenuSectionComponent } from 'src/app/shared/menu/menu-section/abstract-menu-section.component'; + +import { BtnDisabledDirective } from '../../../btn-disabled.directive'; +import { MenuService } from '../../../menu/menu.service'; +import { MenuID } from '../../../menu/menu-id.model'; +import { MenuSection } from '../../../menu/menu-section.model'; + +/** + * Represents a non-expandable section in the dso public menus + */ +@Component({ + selector: 'ds-dso-public-menu-section', + templateUrl: './dso-public-menu-section.component.html', + styleUrls: ['./dso-public-menu-section.component.scss'], + imports: [ + BtnDisabledDirective, + NgbTooltip, + RouterLink, + TranslateModule, + ], +}) +export class DsoPublicMenuSectionComponent extends AbstractMenuSectionComponent implements OnInit { + + menuID: MenuID = MenuID.DSO_PUBLIC; + itemModel; + hasLink: boolean; + canActivate: boolean; + + constructor( + @Inject('sectionDataProvider') protected section: MenuSection, + protected menuService: MenuService, + protected injector: Injector, + ) { + super(menuService, injector); + this.itemModel = section.model; + } + + ngOnInit(): void { + this.hasLink = isNotEmpty(this.itemModel?.link); + this.canActivate = isNotEmpty(this.itemModel?.function); + super.ngOnInit(); + } + + /** + * Activate the section's model function + */ + public activate(event: any) { + event.preventDefault(); + if (!this.itemModel.disabled) { + this.itemModel.function(); + } + event.stopPropagation(); + } +} diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.html b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.html new file mode 100644 index 00000000000..30f383f69e6 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.html @@ -0,0 +1,11 @@ +
+ @for (section of (sections | async); track section) { +
+ + +
+ } +
diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.scss b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.scss new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.spec.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.spec.ts new file mode 100644 index 00000000000..727779c0001 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.spec.ts @@ -0,0 +1,119 @@ +import { + Injector, + NO_ERRORS_SCHEMA, +} from '@angular/core'; +import { + ComponentFixture, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AuthService } from '@dspace/core/auth/auth.service'; +import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service'; +import { AuthServiceStub } from '@dspace/core/testing/auth-service.stub'; +import { TranslateModule } from '@ngx-translate/core'; +import { of } from 'rxjs'; + +import { MenuService } from '../../menu/menu.service'; +import { TextMenuItemModel } from '../../menu/menu-item/models/text.model'; +import { MenuServiceStub } from '../../menu/menu-service.stub'; +import { getMockThemeService } from '../../theme-support/test/theme-service.mock'; +import { ThemeService } from '../../theme-support/theme.service'; +import { DsoPublicMenuComponent } from './dso-public-menu.component'; + +describe('DsoPublicMenuComponent', () => { + let comp: DsoPublicMenuComponent; + let fixture: ComponentFixture; + const menuService = new MenuServiceStub(); + let authorizationService: AuthorizationDataService; + + const routeStub = { + children: [], + }; + + const section = { + id: 'public-dso', + active: false, + visible: true, + model: { + text: 'section-text', + type: null, + disabled: false, + } as TextMenuItemModel, + icon: 'eye', + index: 1, + }; + + const subSection = { + id: 'public-dso-sub', + active: false, + visible: true, + model: { + text: 'sub-section-text', + type: null, + disabled: false, + } as TextMenuItemModel, + icon: 'info-circle', + index: 0, + }; + + beforeEach(waitForAsync(() => { + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: of(true), + }); + spyOn(menuService, 'getMenuTopSections').and.returnValue(of([section])); + spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(of([subSection])); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule, DsoPublicMenuComponent], + providers: [ + Injector, + { provide: MenuService, useValue: menuService }, + { provide: AuthService, useClass: AuthServiceStub }, + { provide: ActivatedRoute, useValue: routeStub }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: ThemeService, useValue: getMockThemeService() }, + ], + schemas: [NO_ERRORS_SCHEMA], + }).compileComponents(); + })); + + describe('onInit', () => { + it('should create', () => { + fixture = TestBed.createComponent(DsoPublicMenuComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + expect(comp).toBeTruthy(); + }); + + it('should have role menubar when subsections exist', () => { + (menuService.getSubSectionsByParentID as jasmine.Spy).and.returnValue(of([subSection])); + fixture = TestBed.createComponent(DsoPublicMenuComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + + const menu = fixture.nativeElement.querySelector('.dso-public-menu'); + expect(menu.getAttribute('role')).toBe('menubar'); + }); + + it('should NOT have role menubar when no subsections exist', () => { + (menuService.getSubSectionsByParentID as jasmine.Spy).and.returnValue(of([])); + fixture = TestBed.createComponent(DsoPublicMenuComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + + const menu = fixture.nativeElement.querySelector('.dso-public-menu'); + expect(menu.getAttribute('role')).toBeNull(); + }); + + it('should have aria-hidden when no subsections exist', () => { + (menuService.getSubSectionsByParentID as jasmine.Spy).and.returnValue(of([])); + fixture = TestBed.createComponent(DsoPublicMenuComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + + const menu = fixture.nativeElement.querySelector('.dso-public-menu'); + expect(menu.getAttribute('aria-hidden')).toBe('true'); + }); + }); +}); diff --git a/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.ts b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.ts new file mode 100644 index 00000000000..3e1013fac77 --- /dev/null +++ b/src/app/shared/dso-page/dso-public-menu/dso-public-menu.component.ts @@ -0,0 +1,75 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +import { + AsyncPipe, + NgComponentOutlet, +} from '@angular/common'; +import { + Component, + Injector, +} from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { AuthorizationDataService } from '@dspace/core/data/feature-authorization/authorization-data.service'; +import { + combineLatest, + Observable, + of, +} from 'rxjs'; +import { + map, + switchMap, +} from 'rxjs/operators'; + +import { MenuComponent } from '../../menu/menu.component'; +import { MenuService } from '../../menu/menu.service'; +import { MenuID } from '../../menu/menu-id.model'; +import { ThemeService } from '../../theme-support/theme.service'; + +/** + * Component that renders the DSO public menu. + * This menu holds item menu voices available to unauthenticated users. + */ +@Component({ + selector: 'ds-dso-public-menu', + styleUrls: ['./dso-public-menu.component.scss'], + templateUrl: './dso-public-menu.component.html', + imports: [ + AsyncPipe, + NgComponentOutlet, + ], +}) +export class DsoPublicMenuComponent extends MenuComponent { + + menuID = MenuID.DSO_PUBLIC; + + menuVisibleWithSections$: Observable; + + constructor( + protected menuService: MenuService, + protected injector: Injector, + public authorizationService: AuthorizationDataService, + public route: ActivatedRoute, + protected themeService: ThemeService, + ) { + super(menuService, injector, authorizationService, route, themeService); + this.menuVisibleWithSections$ = this.menuService.getMenuTopSections(MenuID.DSO_PUBLIC).pipe( + switchMap((sections) => { + if (sections.length === 0) {return of(false);} + return combineLatest( + sections.map((section) => + this.menuService.getSubSectionsByParentID(MenuID.DSO_PUBLIC, section.id).pipe( + map((subSections) => subSections.length > 0), + ), + ), + ).pipe( + map((results) => results.some((hasVisible) => hasVisible)), + ); + }), + ); + } +} diff --git a/src/app/shared/menu/initial-menus-state.ts b/src/app/shared/menu/initial-menus-state.ts index 4ddef8d276a..07f0d1c89e9 100644 --- a/src/app/shared/menu/initial-menus-state.ts +++ b/src/app/shared/menu/initial-menus-state.ts @@ -32,4 +32,13 @@ export const initialMenusState: MenusState = { sections: {}, sectionToSubsectionIndex: {}, }, + [MenuID.DSO_PUBLIC]: + { + id: MenuID.DSO_PUBLIC, + collapsed: true, + previewCollapsed: true, + visible: true, + sections: {}, + sectionToSubsectionIndex: {}, + }, }; diff --git a/src/app/shared/menu/menu-id.model.ts b/src/app/shared/menu/menu-id.model.ts index 547071aa040..2ca8f52498e 100644 --- a/src/app/shared/menu/menu-id.model.ts +++ b/src/app/shared/menu/menu-id.model.ts @@ -4,5 +4,6 @@ export enum MenuID { ADMIN = 'admin-sidebar', PUBLIC = 'public', - DSO_EDIT = 'dso-edit' + DSO_EDIT = 'dso-edit', + DSO_PUBLIC = 'dso-public', } diff --git a/src/app/shared/menu/menu-provider.service.spec.ts b/src/app/shared/menu/menu-provider.service.spec.ts index 5f68e76c224..7b0e39d7596 100644 --- a/src/app/shared/menu/menu-provider.service.spec.ts +++ b/src/app/shared/menu/menu-provider.service.spec.ts @@ -168,6 +168,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); @@ -189,6 +190,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); @@ -213,6 +215,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); @@ -234,6 +237,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); @@ -256,6 +260,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); @@ -275,6 +280,7 @@ describe('MenuProviderService', () => { expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.ADMIN, sectionToBeRemoved.id); expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_EDIT, sectionToBeRemoved.id); + expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.DSO_PUBLIC, sectionToBeRemoved.id); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider1.menuID, expectedSection1); expect(menuService.addSection).not.toHaveBeenCalledWith(persistentProvider2.menuID, expectedSection21); diff --git a/src/app/shared/menu/menu-section.decorator.ts b/src/app/shared/menu/menu-section.decorator.ts index 99b7f30dc91..e36ccc79a02 100644 --- a/src/app/shared/menu/menu-section.decorator.ts +++ b/src/app/shared/menu/menu-section.decorator.ts @@ -6,6 +6,8 @@ import { ThemedExpandableNavbarSectionComponent } from '../../navbar/expandable- import { NavbarSectionComponent } from '../../navbar/navbar-section/navbar-section.component'; import { DsoEditMenuExpandableSectionComponent } from '../dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component'; import { DsoEditMenuSectionComponent } from '../dso-page/dso-edit-menu/dso-edit-menu-section/dso-edit-menu-section.component'; +import { DsoPublicMenuExpandableSectionComponent } from '../dso-page/dso-public-menu/dso-public-expandable-menu-section/dso-public-menu-expandable-section.component'; +import { DsoPublicMenuSectionComponent } from '../dso-page/dso-public-menu/dso-public-menu-section/dso-public-menu-section.component'; import { DEFAULT_THEME } from '../object-collection/shared/listable-object/listable-object.decorator'; import { MenuID } from './menu-id.model'; @@ -26,6 +28,11 @@ menuComponentMap.get(MenuID.DSO_EDIT).set(false, new Map()); menuComponentMap.get(MenuID.DSO_EDIT).get(false).set(DEFAULT_THEME, DsoEditMenuSectionComponent); menuComponentMap.get(MenuID.DSO_EDIT).set(true, new Map()); menuComponentMap.get(MenuID.DSO_EDIT).get(true).set(DEFAULT_THEME, DsoEditMenuExpandableSectionComponent); +menuComponentMap.set(MenuID.DSO_PUBLIC, new Map()); +menuComponentMap.get(MenuID.DSO_PUBLIC).set(false, new Map()); +menuComponentMap.get(MenuID.DSO_PUBLIC).get(false).set(DEFAULT_THEME, DsoPublicMenuSectionComponent); +menuComponentMap.get(MenuID.DSO_PUBLIC).set(true, new Map()); +menuComponentMap.get(MenuID.DSO_PUBLIC).get(true).set(DEFAULT_THEME, DsoPublicMenuExpandableSectionComponent); /** * Decorator function to render a MenuSection for a menu diff --git a/src/app/shared/menu/menu.structure.spec.ts b/src/app/shared/menu/menu.structure.spec.ts index 508bc13b1fe..a704bf6f138 100644 --- a/src/app/shared/menu/menu.structure.spec.ts +++ b/src/app/shared/menu/menu.structure.spec.ts @@ -68,6 +68,8 @@ describe('buildMenuStructure', () => { ), ]), ], + [MenuID.DSO_PUBLIC]: [ + ], }; const orderedProviderTypeList = From 1abb1696fe982cc0dedb6378f0a4479778ff65c5 Mon Sep 17 00:00:00 2001 From: FrancescoMolinaro Date: Mon, 3 Aug 2026 12:21:02 +0200 Subject: [PATCH 2/4] [DURACOM-501] add export item menu and functionalities, add dso-public menu to items --- src/app/app.menus.ts | 4 + src/app/core/data/collection-data.service.ts | 53 ++- .../data/processes/script-data.service.ts | 2 + .../item-export-format.service.spec.ts | 191 ++++++++ .../item-export-format.service.ts | 258 +++++++++++ .../model/item-export-format.model.ts | 64 +++ .../model/item-export-format.resource-type.ts | 6 + .../models/process-notification.model.ts | 77 ++++ .../notifications.service.ts | 15 + .../testing/notifications-service.stub.ts | 1 + .../journal-issue.component.html | 1 + .../journal-issue/journal-issue.component.ts | 2 + .../journal-volume.component.html | 1 + .../journal-volume.component.ts | 2 + .../item-pages/journal/journal.component.html | 1 + .../journal/journal.component.spec.ts | 2 + .../item-pages/journal/journal.component.ts | 2 + .../org-unit/org-unit.component.html | 1 + .../item-pages/org-unit/org-unit.component.ts | 2 + .../item-pages/person/person.component.html | 1 + .../item-pages/person/person.component.ts | 2 + .../item-pages/project/project.component.html | 1 + .../item-pages/project/project.component.ts | 2 + .../full/full-item-page.component.html | 1 + .../full/full-item-page.component.spec.ts | 2 + .../full/full-item-page.component.ts | 2 + .../item-types/dataset/dataset.component.html | 1 + .../dataset/dataset.component.spec.ts | 3 +- .../item-types/dataset/dataset.component.ts | 2 + .../publication/publication.component.html | 1 + .../publication/publication.component.spec.ts | 3 +- .../publication/publication.component.ts | 2 + .../item-types/shared/item.component.spec.ts | 2 + .../untyped-item/untyped-item.component.html | 1 + .../untyped-item.component.spec.ts | 2 + .../untyped-item/untyped-item.component.ts | 2 + ...ered-collection-selector.component.spec.ts | 92 ++++ ...inistered-collection-selector.component.ts | 105 +++++ src/app/shared/listable.module.ts | 2 + .../menu/providers/export-item.menu.spec.ts | 129 ++++++ .../shared/menu/providers/export-item.menu.ts | 86 ++++ .../selectable-list.actions.ts | 10 + .../selectable-list.service.ts | 9 + .../item-export-alert.component.html | 20 + .../item-export-alert.component.spec.ts | 60 +++ .../item-export-alert.component.ts | 33 ++ .../item-export-modal-launcher.component.html | 26 ++ .../item-export-modal-launcher.component.scss | 3 + ...em-export-modal-launcher.component.spec.ts | 176 ++++++++ .../item-export-modal-launcher.component.ts | 147 ++++++ .../item-export/item-export.service.spec.ts | 146 ++++++ .../search/item-export/item-export.service.ts | 130 ++++++ .../item-export-list.component.html | 27 ++ .../item-export-list.component.scss | 0 .../item-export-list.component.spec.ts | 93 ++++ .../item-export-list.component.ts | 112 +++++ .../item-export/item-export.component.html | 115 +++++ .../item-export/item-export.component.spec.ts | 422 ++++++++++++++++++ .../item-export/item-export.component.ts | 331 ++++++++++++++ .../journal-issue/journal-issue.component.ts | 2 + .../journal-volume.component.ts | 2 + .../item-pages/journal/journal.component.ts | 2 + .../item-pages/person/person.component.ts | 2 + .../full/full-item-page.component.ts | 2 + .../publication/publication.component.ts | 2 + .../untyped-item/untyped-item.component.ts | 2 + 66 files changed, 2999 insertions(+), 4 deletions(-) create mode 100644 src/app/core/itemexportformat/item-export-format.service.spec.ts create mode 100644 src/app/core/itemexportformat/item-export-format.service.ts create mode 100644 src/app/core/itemexportformat/model/item-export-format.model.ts create mode 100644 src/app/core/itemexportformat/model/item-export-format.resource-type.ts create mode 100644 src/app/core/notification-system/models/process-notification.model.ts create mode 100644 src/app/shared/dso-selector/dso-selector/administered-collection-selector/administered-collection-selector.component.spec.ts create mode 100644 src/app/shared/dso-selector/dso-selector/administered-collection-selector/administered-collection-selector.component.ts create mode 100644 src/app/shared/menu/providers/export-item.menu.spec.ts create mode 100644 src/app/shared/menu/providers/export-item.menu.ts create mode 100644 src/app/shared/search/item-export/item-export-alert/item-export-alert.component.html create mode 100644 src/app/shared/search/item-export/item-export-alert/item-export-alert.component.spec.ts create mode 100644 src/app/shared/search/item-export/item-export-alert/item-export-alert.component.ts create mode 100644 src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.html create mode 100644 src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.scss create mode 100644 src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.spec.ts create mode 100644 src/app/shared/search/item-export/item-export-modal-launcher/item-export-modal-launcher.component.ts create mode 100644 src/app/shared/search/item-export/item-export.service.spec.ts create mode 100644 src/app/shared/search/item-export/item-export.service.ts create mode 100644 src/app/shared/search/item-export/item-export/item-export-list/item-export-list.component.html create mode 100644 src/app/shared/search/item-export/item-export/item-export-list/item-export-list.component.scss create mode 100644 src/app/shared/search/item-export/item-export/item-export-list/item-export-list.component.spec.ts create mode 100644 src/app/shared/search/item-export/item-export/item-export-list/item-export-list.component.ts create mode 100644 src/app/shared/search/item-export/item-export/item-export.component.html create mode 100644 src/app/shared/search/item-export/item-export/item-export.component.spec.ts create mode 100644 src/app/shared/search/item-export/item-export/item-export.component.ts diff --git a/src/app/app.menus.ts b/src/app/app.menus.ts index 23df43a7d27..83c2fba7814 100644 --- a/src/app/app.menus.ts +++ b/src/app/app.menus.ts @@ -26,6 +26,7 @@ import { EditCMSMetadataMenuProvider } from './shared/menu/providers/edit-cms-me import { EditItemMenuProvider } from './shared/menu/providers/edit-item-details.menu'; import { EditUserAgreementMenuProvider } from './shared/menu/providers/edit-user-agreement.menu'; import { ExportMenuProvider } from './shared/menu/providers/export.menu'; +import { ExportItemMenuProvider } from './shared/menu/providers/export-item.menu'; import { HealthMenuProvider } from './shared/menu/providers/health.menu'; import { ImportMenuProvider } from './shared/menu/providers/import.menu'; import { ClaimMenuProvider } from './shared/menu/providers/item-claim.menu'; @@ -120,5 +121,8 @@ export const MENUS = buildMenuStructure({ ]), ], [MenuID.DSO_PUBLIC]: [ + ExportItemMenuProvider.onRoute( + MenuRoute.ITEM_PAGE, + ), ], }); diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 23e12907faa..19aa9911e6b 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -151,6 +151,55 @@ export class CollectionDataService extends ComColDataService { ); } + /** + * Get all collections the user is admin + * + * @param query limit the returned collection to those with metadata values matching the query terms. + * @param options The [[FindListOptions]] object + * @param reRequestOnStale Whether or not the request should automatically be re-requested after + * the response becomes stale + * @param linksToFollow The array of [[FollowLinkConfig]] + * @return Observable>> + * collection list + */ + getAdministeredCollection(query: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + const searchHref = 'findAdministered'; + options = Object.assign({}, options, { + searchParams: [new RequestParam('query', query)], + }); + + return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe( + getAllCompletedRemoteData(), + ); + } + + /** + * Get all collections the user is admin selected by entityType + * + * @param query limit the returned collection to those with metadata values matching the query terms. + * @param entityType mandatory, the label of the entity type field the collection must have. + * @param options The [[FindListOptions]] object + * @param reRequestOnStale Whether or not the request should automatically be re-requested after + * the response becomes stale + * @param linksToFollow The array of [[FollowLinkConfig]] + * @return Observable>> + * collection list + */ + getAdministeredCollectionByEntityType(query: string, entityType: string, options: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig[]): Observable>> { + + const searchHref = 'findAdminAuthorizedByEntityType'; + options = Object.assign({}, options, { + searchParams: [ + new RequestParam('query', query), + new RequestParam('entityType', entityType), + ], + }); + + return this.searchBy(searchHref, options, true, reRequestOnStale, ...linksToFollow).pipe( + getAllCompletedRemoteData(), + ); + } + /** * Get all collections the user is authorized to submit to * @@ -251,8 +300,8 @@ export class CollectionDataService extends ComColDataService { options.elementsPerPage = 1; return this.searchBy(searchHref, options).pipe( - getFirstCompletedRemoteData(), - map((collections: RemoteData>) => collections?.payload?.totalElements > 0), + getAllCompletedRemoteData(), + map((collections: RemoteData>) => collections.payload.totalElements > 0), ); } diff --git a/src/app/core/data/processes/script-data.service.ts b/src/app/core/data/processes/script-data.service.ts index 2388feb42b2..4801ae343fd 100644 --- a/src/app/core/data/processes/script-data.service.ts +++ b/src/app/core/data/processes/script-data.service.ts @@ -32,6 +32,8 @@ export const METADATA_EXPORT_SCRIPT_NAME = 'metadata-export'; export const BATCH_IMPORT_SCRIPT_NAME = 'import'; export const BATCH_EXPORT_SCRIPT_NAME = 'export'; export const DSPACE_OBJECT_DELETION_SCRIPT_NAME = 'object-deletion'; +export const ITEM_EXPORT_SCRIPT_NAME = 'item-export'; +export const BULK_ITEM_EXPORT_SCRIPT_NAME = 'bulk-item-export'; @Injectable({ providedIn: 'root' }) export class ScriptDataService extends IdentifiableDataService