diff --git a/package.json b/package.json
index bd7827e333..18f3bf44e5 100644
--- a/package.json
+++ b/package.json
@@ -132,7 +132,8 @@
"sortablejs": "1.10.1",
"tslib": "^2.0.0",
"webfontloader": "1.6.28",
- "zone.js": "^0.10.3"
+ "zone.js": "^0.10.3",
+ "@kolkov/ngx-gallery": "^1.2.3"
},
"devDependencies": {
"@angular-builders/custom-webpack": "10.0.1",
diff --git a/src/app/+admin/admin-routing-paths.ts b/src/app/+admin/admin-routing-paths.ts
index 11eac49fe2..3168ea93c9 100644
--- a/src/app/+admin/admin-routing-paths.ts
+++ b/src/app/+admin/admin-routing-paths.ts
@@ -2,12 +2,7 @@ import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getAdminModuleRoute } from '../app-routing-paths';
export const REGISTRIES_MODULE_PATH = 'registries';
-export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
export function getRegistriesModuleRoute() {
return new URLCombiner(getAdminModuleRoute(), REGISTRIES_MODULE_PATH).toString();
}
-
-export function getAccessControlModuleRoute() {
- return new URLCombiner(getAdminModuleRoute(), ACCESS_CONTROL_MODULE_PATH).toString();
-}
diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts
index 6c44af8190..ee5cb8737b 100644
--- a/src/app/+admin/admin-routing.module.ts
+++ b/src/app/+admin/admin-routing.module.ts
@@ -6,7 +6,7 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow-page.component';
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
-import { ACCESS_CONTROL_MODULE_PATH, REGISTRIES_MODULE_PATH } from './admin-routing-paths';
+import { REGISTRIES_MODULE_PATH } from './admin-routing-paths';
@NgModule({
imports: [
@@ -16,11 +16,6 @@ import { ACCESS_CONTROL_MODULE_PATH, REGISTRIES_MODULE_PATH } from './admin-rout
loadChildren: () => import('./admin-registries/admin-registries.module')
.then((m) => m.AdminRegistriesModule),
},
- {
- path: ACCESS_CONTROL_MODULE_PATH,
- loadChildren: () => import('./admin-access-control/admin-access-control.module')
- .then((m) => m.AdminAccessControlModule),
- },
{
path: 'search',
resolve: { breadcrumb: I18nBreadcrumbResolver },
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
index ec571ff3d5..0a9ef512d7 100644
--- a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
@@ -16,6 +16,8 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { RouterTestingModule } from '@angular/router/testing';
import { ActivatedRoute } from '@angular/router';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../../core/data/feature-authorization/feature-id';
+import createSpy = jasmine.createSpy;
describe('AdminSidebarComponent', () => {
let comp: AdminSidebarComponent;
@@ -170,4 +172,150 @@ describe('AdminSidebarComponent', () => {
expect(menuService.collapseMenuPreview).toHaveBeenCalled();
}));
});
+
+ describe('menu', () => {
+ beforeEach(() => {
+ spyOn(menuService, 'addSection');
+ });
+
+ describe('for regular user', () => {
+ beforeEach(() => {
+ authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake(() => {
+ return observableOf(false);
+ });
+ });
+
+ beforeEach(() => {
+ comp.createMenu();
+ });
+
+ it('should not show site admin section', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'admin_search', visible: false,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'registries', visible: false,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ parentID: 'registries', visible: false,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'curation_tasks', visible: false,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'workflow', visible: false,
+ }));
+ });
+
+ it('should not show edit_community', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'edit_community', visible: false,
+ }));
+
+ });
+
+ it('should not show edit_collection', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'edit_collection', visible: false,
+ }));
+ });
+
+ it('should not show access control section', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'access_control', visible: false,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ parentID: 'access_control', visible: false,
+ }));
+
+ });
+ });
+
+ describe('for site admin', () => {
+ beforeEach(() => {
+ authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
+ return observableOf(featureID === FeatureID.AdministratorOf);
+ });
+ });
+
+ beforeEach(() => {
+ comp.createMenu();
+ });
+
+ it('should contain site admin section', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'admin_search', visible: true,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'registries', visible: true,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ parentID: 'registries', visible: true,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'curation_tasks', visible: true,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'workflow', visible: true,
+ }));
+ });
+ });
+
+ describe('for community admin', () => {
+ beforeEach(() => {
+ authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
+ return observableOf(featureID === FeatureID.IsCommunityAdmin);
+ });
+ });
+
+ beforeEach(() => {
+ comp.createMenu();
+ });
+
+ it('should show edit_community', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'edit_community', visible: true,
+ }));
+ });
+ });
+
+ describe('for collection admin', () => {
+ beforeEach(() => {
+ authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
+ return observableOf(featureID === FeatureID.IsCollectionAdmin);
+ });
+ });
+
+ beforeEach(() => {
+ comp.createMenu();
+ });
+
+ it('should show edit_collection', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'edit_collection', visible: true,
+ }));
+ });
+ });
+
+ describe('for group admin', () => {
+ beforeEach(() => {
+ authorizationService.isAuthorized = createSpy('isAuthorized').and.callFake((featureID: FeatureID) => {
+ return observableOf(featureID === FeatureID.CanManageGroups);
+ });
+ });
+
+ beforeEach(() => {
+ comp.createMenu();
+ });
+
+ it('should show access control section', () => {
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ id: 'access_control', visible: true,
+ }));
+ expect(menuService.addSection).toHaveBeenCalledWith(comp.menuID, jasmine.objectContaining({
+ parentID: 'access_control', visible: true,
+ }));
+ });
+ });
+ });
});
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
index bfc36cea24..3bf5938487 100644
--- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
@@ -1,6 +1,6 @@
import { Component, Injector, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
+import { combineLatest, combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { first, map, take } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
import { ScriptDataService } from '../../core/data/processes/script-data.service';
@@ -76,9 +76,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
*/
ngOnInit(): void {
this.createMenu();
- this.createSiteAdministratorMenuSections();
- this.createExportMenuSections();
- this.createImportMenuSections();
super.ngOnInit();
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
this.authService.isAuthenticated()
@@ -102,192 +99,210 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
* Initialize all menu sections and items for this menu
*/
createMenu() {
- const menuList = [
- /* News */
- {
- id: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.TEXT,
- text: 'menu.section.new'
- } as TextMenuItemModel,
- icon: 'plus-circle',
- index: 0
- },
- {
- id: 'new_community',
- parentID: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.new_community',
- function: () => {
- this.modalService.open(CreateCommunityParentSelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
- {
- id: 'new_collection',
- parentID: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.new_collection',
- function: () => {
- this.modalService.open(CreateCollectionParentSelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
- {
- id: 'new_item',
- parentID: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.new_item',
- function: () => {
- this.modalService.open(CreateItemParentSelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
- {
- id: 'new_process',
- parentID: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.new_process',
- link: '/processes/new'
- } as LinkMenuItemModel,
- },
- {
- id: 'new_item_version',
- parentID: 'new',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.new_item_version',
- link: ''
- } as LinkMenuItemModel,
- },
+ this.createMainMenuSections();
+ this.createSiteAdministratorMenuSections();
+ this.createExportMenuSections();
+ this.createImportMenuSections();
+ this.createAccessControlMenuSections();
+ }
- /* Edit */
- {
- id: 'edit',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.TEXT,
- text: 'menu.section.edit'
- } as TextMenuItemModel,
- icon: 'pencil-alt',
- index: 1
- },
- {
- id: 'edit_community',
- parentID: 'edit',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.edit_community',
- function: () => {
- this.modalService.open(EditCommunitySelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
- {
- id: 'edit_collection',
- parentID: 'edit',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.edit_collection',
- function: () => {
- this.modalService.open(EditCollectionSelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
- {
- id: 'edit_item',
- parentID: 'edit',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.ONCLICK,
- text: 'menu.section.edit_item',
- function: () => {
- this.modalService.open(EditItemSelectorComponent);
- }
- } as OnClickMenuItemModel,
- },
+ /**
+ * Initialize the main menu sections.
+ * edit_community / edit_collection is only included if the current user is a Community or Collection admin
+ */
+ createMainMenuSections() {
+ combineLatest([
+ this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
+ this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
+ this.authorizationService.isAuthorized(FeatureID.AdministratorOf)
+ ]).subscribe(([isCollectionAdmin, isCommunityAdmin, isSiteAdmin]) => {
+ const menuList = [
+ /* News */
+ {
+ id: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.new'
+ } as TextMenuItemModel,
+ icon: 'plus-circle',
+ index: 0
+ },
+ {
+ id: 'new_community',
+ parentID: 'new',
+ active: false,
+ visible: isCommunityAdmin,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.new_community',
+ function: () => {
+ this.modalService.open(CreateCommunityParentSelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
+ {
+ id: 'new_collection',
+ parentID: 'new',
+ active: false,
+ visible: isCommunityAdmin,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.new_collection',
+ function: () => {
+ this.modalService.open(CreateCollectionParentSelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
+ {
+ id: 'new_item',
+ parentID: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.new_item',
+ function: () => {
+ this.modalService.open(CreateItemParentSelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
+ {
+ id: 'new_process',
+ parentID: 'new',
+ active: false,
+ visible: isCollectionAdmin,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.new_process',
+ link: '/processes/new'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'new_item_version',
+ parentID: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.new_item_version',
+ link: ''
+ } as LinkMenuItemModel,
+ },
- /* Curation tasks */
- {
- id: 'curation_tasks',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.curation_task',
- link: ''
- } as LinkMenuItemModel,
- icon: 'filter',
- index: 7
- },
+ /* Edit */
+ {
+ id: 'edit',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.edit'
+ } as TextMenuItemModel,
+ icon: 'pencil-alt',
+ index: 1
+ },
+ {
+ id: 'edit_community',
+ parentID: 'edit',
+ active: false,
+ visible: isCommunityAdmin,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.edit_community',
+ function: () => {
+ this.modalService.open(EditCommunitySelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
+ {
+ id: 'edit_collection',
+ parentID: 'edit',
+ active: false,
+ visible: isCollectionAdmin,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.edit_collection',
+ function: () => {
+ this.modalService.open(EditCollectionSelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
+ {
+ id: 'edit_item',
+ parentID: 'edit',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.ONCLICK,
+ text: 'menu.section.edit_item',
+ function: () => {
+ this.modalService.open(EditItemSelectorComponent);
+ }
+ } as OnClickMenuItemModel,
+ },
- /* Statistics */
- {
- id: 'statistics_task',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.statistics_task',
- link: ''
- } as LinkMenuItemModel,
- icon: 'chart-bar',
- index: 8
- },
+ /* Curation tasks */
+ {
+ id: 'curation_tasks',
+ active: false,
+ visible: isCollectionAdmin,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.curation_task',
+ link: ''
+ } as LinkMenuItemModel,
+ icon: 'filter',
+ index: 7
+ },
- /* Control Panel */
- {
- id: 'control_panel',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.control_panel',
- link: ''
- } as LinkMenuItemModel,
- icon: 'cogs',
- index: 9
- },
+ /* Statistics */
+ {
+ id: 'statistics_task',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.statistics_task',
+ link: ''
+ } as LinkMenuItemModel,
+ icon: 'chart-bar',
+ index: 8
+ },
- /* Processes */
- {
- id: 'processes',
- active: false,
- visible: true,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.processes',
- link: '/processes'
- } as LinkMenuItemModel,
- icon: 'terminal',
- index: 10
- },
- ];
- menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
- shouldPersistOnRouteChange: true
- })));
+ /* Control Panel */
+ {
+ id: 'control_panel',
+ active: false,
+ visible: isSiteAdmin,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.control_panel',
+ link: ''
+ } as LinkMenuItemModel,
+ icon: 'cogs',
+ index: 9
+ },
+
+ /* Processes */
+ {
+ id: 'processes',
+ active: false,
+ visible: isSiteAdmin,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.processes',
+ link: '/processes'
+ } as LinkMenuItemModel,
+ icon: 'terminal',
+ index: 10
+ },
+ ];
+ menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
+ shouldPersistOnRouteChange: true
+ })));
+ });
}
/**
@@ -436,51 +451,6 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
createSiteAdministratorMenuSections() {
this.authorizationService.isAuthorized(FeatureID.AdministratorOf).subscribe((authorized) => {
const menuList = [
- /* Access Control */
- {
- id: 'access_control',
- active: false,
- visible: authorized,
- model: {
- type: MenuItemType.TEXT,
- text: 'menu.section.access_control'
- } as TextMenuItemModel,
- icon: 'key',
- index: 4
- },
- {
- id: 'access_control_people',
- parentID: 'access_control',
- active: false,
- visible: authorized,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.access_control_people',
- link: '/admin/access-control/epeople'
- } as LinkMenuItemModel,
- },
- {
- id: 'access_control_groups',
- parentID: 'access_control',
- active: false,
- visible: authorized,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.access_control_groups',
- link: '/admin/access-control/groups'
- } as LinkMenuItemModel,
- },
- {
- id: 'access_control_authorizations',
- parentID: 'access_control',
- active: false,
- visible: authorized,
- model: {
- type: MenuItemType.LINK,
- text: 'menu.section.access_control_authorizations',
- link: ''
- } as LinkMenuItemModel,
- },
/* Admin Search */
{
id: 'admin_search',
@@ -564,6 +534,65 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
});
}
+ /**
+ * Create menu sections dependent on whether or not the current user can manage access control groups
+ */
+ createAccessControlMenuSections() {
+ this.authorizationService.isAuthorized(FeatureID.CanManageGroups).subscribe((authorized) => {
+ const menuList = [
+ /* Access Control */
+ {
+ id: 'access_control_people',
+ parentID: 'access_control',
+ active: false,
+ visible: authorized,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_people',
+ link: '/access-control/epeople'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'access_control_groups',
+ parentID: 'access_control',
+ active: false,
+ visible: authorized,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_groups',
+ link: '/access-control/groups'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'access_control_authorizations',
+ parentID: 'access_control',
+ active: false,
+ visible: authorized,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_authorizations',
+ link: ''
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'access_control',
+ active: false,
+ visible: authorized,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.access_control'
+ } as TextMenuItemModel,
+ icon: 'key',
+ index: 4
+ },
+ ];
+
+ menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
+ shouldPersistOnRouteChange: true,
+ })));
+ });
+ }
+
/**
* Method to change this.collapsed to false when the slide animation ends and is sliding open
* @param event The animation event
diff --git a/src/app/+admin/admin.module.ts b/src/app/+admin/admin.module.ts
index 494edd71eb..25cdd67dcf 100644
--- a/src/app/+admin/admin.module.ts
+++ b/src/app/+admin/admin.module.ts
@@ -1,6 +1,6 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
-import { AdminAccessControlModule } from './admin-access-control/admin-access-control.module';
+import { AccessControlModule } from '../access-control/access-control.module';
import { MetadataImportPageComponent } from './admin-import-metadata-page/metadata-import-page.component';
import { AdminRegistriesModule } from './admin-registries/admin-registries.module';
import { AdminRoutingModule } from './admin-routing.module';
@@ -21,7 +21,7 @@ const ENTRY_COMPONENTS = [
imports: [
AdminRoutingModule,
AdminRegistriesModule,
- AdminAccessControlModule,
+ AccessControlModule,
AdminSearchModule.withEntryComponents(),
AdminWorkflowModuleModule.withEntryComponents(),
SharedModule,
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index bbe2cb5e66..ed0ad1f021 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -35,7 +35,7 @@
-
+
diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts
index 5d76ce7859..6435df55ee 100644
--- a/src/app/+collection-page/collection-page.component.ts
+++ b/src/app/+collection-page/collection-page.component.ts
@@ -27,6 +27,8 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { AuthService } from '../core/auth/auth.service';
import {PaginationChangeEvent} from '../shared/pagination/paginationChangeEvent.interface';
+import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { getCollectionPageRoute } from './collection-page-routing-paths';
@Component({
@@ -50,6 +52,11 @@ export class CollectionPageComponent implements OnInit {
sortConfig: SortOptions
}>;
+ /**
+ * Whether the current user is a Community admin
+ */
+ isCollectionAdmin$: Observable;
+
/**
* Route to the community page
*/
@@ -62,6 +69,7 @@ export class CollectionPageComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private authService: AuthService,
+ private authorizationDataService: AuthorizationDataService,
) {
this.paginationConfig = new PaginationComponentOptions();
this.paginationConfig.id = 'collection-page-pagination';
@@ -81,6 +89,7 @@ export class CollectionPageComponent implements OnInit {
filter((collection: Collection) => hasValue(collection)),
mergeMap((collection: Collection) => collection.logo)
);
+ this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
this.paginationChanges$ = new BehaviorSubject({
paginationConfig: this.paginationConfig,
diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts
index e41f0ebda4..e523cee991 100644
--- a/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts
+++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts
@@ -12,6 +12,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
+import { CollectionAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard';
/**
* Routing module that handles the routing for the Edit Collection page administrator functionality
@@ -26,6 +27,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
},
data: { breadcrumbKey: 'collection.edit' },
component: EditCollectionPageComponent,
+ canActivate: [CollectionAdministratorGuard],
children: [
{
path: '',
diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html
index 1b4f90a52b..cf7282eb4b 100644
--- a/src/app/+community-page/community-page.component.html
+++ b/src/app/+community-page/community-page.component.html
@@ -21,7 +21,7 @@
-
+
diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts
index 7ba8dfc6c0..70259a599b 100644
--- a/src/app/+community-page/community-page.component.ts
+++ b/src/app/+community-page/community-page.component.ts
@@ -15,6 +15,8 @@ import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util';
import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../core/shared/operators';
import { AuthService } from '../core/auth/auth.service';
+import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../core/data/feature-authorization/feature-id';
import { getCommunityPageRoute } from './community-page-routing-paths';
@Component({
@@ -33,6 +35,11 @@ export class CommunityPageComponent implements OnInit {
*/
communityRD$: Observable>;
+ /**
+ * Whether the current user is a Community admin
+ */
+ isCommunityAdmin$: Observable;
+
/**
* The logo of this community
*/
@@ -49,6 +56,7 @@ export class CommunityPageComponent implements OnInit {
private route: ActivatedRoute,
private router: Router,
private authService: AuthService,
+ private authorizationDataService: AuthorizationDataService
) {
}
@@ -66,6 +74,6 @@ export class CommunityPageComponent implements OnInit {
getAllSucceededRemoteDataPayload(),
map((community) => getCommunityPageRoute(community.id))
);
+ this.isCommunityAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCommunityAdmin);
}
-
}
diff --git a/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts b/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts
index 440fa01a30..faebb1ef2e 100644
--- a/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts
+++ b/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts
@@ -10,6 +10,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
+import { CommunityAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/community-administrator.guard';
/**
* Routing module that handles the routing for the Edit Community page administrator functionality
@@ -24,6 +25,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
},
data: { breadcrumbKey: 'community.edit' },
component: EditCommunityPageComponent,
+ canActivate: [CommunityAdministratorGuard],
children: [
{
path: '',
diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts
index 116a0feb21..ea78767df5 100644
--- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.spec.ts
@@ -173,6 +173,19 @@ describe('ItemDeleteComponent', () => {
.toHaveBeenCalledWith(mockItem.id, types.filter((type) => typesSelection[type]).map((type) => type.id));
expect(comp.notify).toHaveBeenCalled();
});
+
+ it('should call delete function from the ItemDataService with empty types', () => {
+
+ spyOn(comp, 'notify');
+ jasmine.getEnv().allowRespy(true);
+ spyOn(entityTypeService, 'getEntityTypeRelationships').and.returnValue([]);
+ comp.ngOnInit();
+
+ comp.performAction();
+
+ expect(mockItemDataService.delete).toHaveBeenCalledWith(mockItem.id, []);
+ expect(comp.notify).toHaveBeenCalled();
+ });
});
describe('notify', () => {
it('should navigate to the homepage on successful deletion of the item', () => {
diff --git a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts
index 366b22bec7..a05ffec4f5 100644
--- a/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts
+++ b/src/app/+item-page/edit-item-page/item-delete/item-delete.component.ts
@@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core';
-import { defaultIfEmpty, filter, map, switchMap, take } from 'rxjs/operators';
+import {defaultIfEmpty, filter, map, switchMap, take} from 'rxjs/operators';
import { AbstractSimpleItemActionComponent } from '../simple-item-action/abstract-simple-item-action.component';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import {
@@ -121,8 +121,11 @@ export class ItemDeleteComponent
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((relationshipTypes) => relationshipTypes.page),
- switchMap((types) =>
- combineLatest(types.map((type) => this.getRelationships(type))).pipe(
+ switchMap((types) => {
+ if (types.length === 0) {
+ return observableOf(types);
+ }
+ return combineLatest(types.map((type) => this.getRelationships(type))).pipe(
map((relationships) =>
types.reduce((includedTypes, type, index) => {
if (!includedTypes.some((includedType) => includedType.id === type.id)
@@ -133,8 +136,8 @@ export class ItemDeleteComponent
}
}, [])
),
- )
- ),
+ );
+ })
);
} else {
this.types$ = observableOf([]);
diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts
index 0f66d5b55e..ede024953b 100644
--- a/src/app/+item-page/item-page.module.ts
+++ b/src/app/+item-page/item-page.module.ts
@@ -27,6 +27,10 @@ import { ResearchEntitiesModule } from '../entity-groups/research-entities/resea
import { ThemedItemPageComponent } from './simple/themed-item-page.component';
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
import { IIIFEntitiesModule } from '../entity-groups/iiif-entities/iiif-entities.module';
+import { MediaViewerComponent } from './media-viewer/media-viewer.component';
+import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
+import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
+import { NgxGalleryModule } from '@kolkov/ngx-gallery';
const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator
@@ -53,6 +57,9 @@ const DECLARATIONS = [
ItemComponent,
UploadBitstreamComponent,
AbstractIncrementalListComponent,
+ MediaViewerComponent,
+ MediaViewerVideoComponent,
+ MediaViewerImageComponent
];
@NgModule({
@@ -65,6 +72,7 @@ const DECLARATIONS = [
JournalEntitiesModule.withEntryComponents(),
ResearchEntitiesModule.withEntryComponents(),
IIIFEntitiesModule.withEntryComponents()
+ NgxGalleryModule,
],
declarations: [
...DECLARATIONS
diff --git a/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.html b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.html
new file mode 100644
index 0000000000..bafc6f079c
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss
new file mode 100644
index 0000000000..72ce4b04d9
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.scss
@@ -0,0 +1,6 @@
+.ngx-gallery {
+ display: inline-block;
+ margin-bottom: 20px;
+ width: 340px !important;
+ height: 279px !important;
+}
diff --git a/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.spec.ts b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.spec.ts
new file mode 100644
index 0000000000..1f1fed789f
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.spec.ts
@@ -0,0 +1,89 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
+import { Bitstream } from '../../../core/shared/bitstream.model';
+import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
+import { MockBitstreamFormat1 } from '../../../shared/mocks/item.mock';
+
+import { MediaViewerImageComponent } from './media-viewer-image.component';
+
+import { of as observableOf } from 'rxjs';
+import { AuthService } from '../../../core/auth/auth.service';
+
+describe('MediaViewerImageComponent', () => {
+ let component: MediaViewerImageComponent;
+ let fixture: ComponentFixture;
+
+ const authService = jasmine.createSpyObj('authService', {
+ isAuthenticated: observableOf(false)
+ });
+
+ const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
+ sizeBytes: 10201,
+ content:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ format: observableOf(MockBitstreamFormat1),
+ bundleName: 'ORIGINAL',
+ _links: {
+ self: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ },
+ content: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ },
+ },
+ id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ type: 'bitstream',
+ metadata: {
+ 'dc.title': [
+ {
+ language: null,
+ value: 'test_word.docx',
+ },
+ ],
+ },
+ });
+
+ const mockMediaViewerItems: MediaViewerItem[] = Object.assign(
+ new Array(),
+ [
+ { bitstream: mockBitstream, format: 'image', thumbnail: null },
+ { bitstream: mockBitstream, format: 'image', thumbnail: null },
+ ]
+ );
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports:[],
+ declarations: [MediaViewerImageComponent],
+ schemas: [NO_ERRORS_SCHEMA],
+ providers: [
+ { provide: AuthService, useValue: authService },
+ ],
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MediaViewerImageComponent);
+ component = fixture.componentInstance;
+ component.galleryOptions = [new NgxGalleryOptions({})];
+ component.galleryImages = component.convertToGalleryImage(
+ mockMediaViewerItems
+ );
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should contain a gallery options', () => {
+ expect(component.galleryOptions.length).toBeGreaterThan(0);
+ });
+
+ it('should contain an image array', () => {
+ expect(component.galleryImages.length).toBeGreaterThan(0);
+ });
+});
diff --git a/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts
new file mode 100644
index 0000000000..0c32b5603d
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-image/media-viewer-image.component.ts
@@ -0,0 +1,88 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { NgxGalleryImage, NgxGalleryOptions } from '@kolkov/ngx-gallery';
+import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
+import { NgxGalleryAnimation } from '@kolkov/ngx-gallery';
+import { Observable } from 'rxjs';
+import { AuthService } from '../../../core/auth/auth.service';
+
+/**
+ * This componenet render an image gallery for the image viewer
+ */
+@Component({
+ selector: 'ds-media-viewer-image',
+ templateUrl: './media-viewer-image.component.html',
+ styleUrls: ['./media-viewer-image.component.scss'],
+})
+export class MediaViewerImageComponent implements OnInit {
+ @Input() images: MediaViewerItem[];
+ @Input() preview?: boolean;
+ @Input() image?: string;
+
+ loggedin: boolean;
+
+ galleryOptions: NgxGalleryOptions[];
+ galleryImages: NgxGalleryImage[];
+
+ /**
+ * Whether or not the current user is authenticated
+ */
+ isAuthenticated$: Observable;
+
+ constructor(private authService: AuthService) {}
+
+ /**
+ * Thi method sets up the gallery settings and data
+ */
+ ngOnInit(): void {
+ this.isAuthenticated$ = this.authService.isAuthenticated();
+ this.galleryOptions = [
+ {
+ preview: this.preview !== undefined ? this.preview : true,
+ image: true,
+ imageSize: 'contain',
+ thumbnails: false,
+ imageArrows: false,
+ startIndex: 0,
+ imageAnimation: NgxGalleryAnimation.Slide,
+ previewCloseOnEsc: true,
+ previewZoom: true,
+ previewRotate: true,
+ previewFullscreen: true,
+ },
+ ];
+
+ if (this.image) {
+ this.galleryImages = [
+ {
+ small: this.image,
+ medium: this.image,
+ big: this.image,
+ },
+ ];
+ } else {
+ this.galleryImages = this.convertToGalleryImage(this.images);
+ }
+ }
+
+ /**
+ * This method convert an array of MediaViewerItem into NgxGalleryImage array
+ * @param medias input NgxGalleryImage array
+ */
+ convertToGalleryImage(medias: MediaViewerItem[]): NgxGalleryImage[] {
+ const mappadImages = [];
+ for (const image of medias) {
+ if (image.format === 'image') {
+ mappadImages.push({
+ small: image.thumbnail
+ ? image.thumbnail
+ : './assets/images/replacement_image.svg',
+ medium: image.thumbnail
+ ? image.thumbnail
+ : './assets/images/replacement_image.svg',
+ big: image.bitstream._links.content.href,
+ });
+ }
+ }
+ return mappadImages;
+ }
+}
diff --git a/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.html b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.html
new file mode 100644
index 0000000000..a4493e36fc
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.html
@@ -0,0 +1,47 @@
+
+
diff --git a/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss
new file mode 100644
index 0000000000..7702da7361
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.scss
@@ -0,0 +1,4 @@
+video {
+ width: 340px;
+ height: 279px;
+}
diff --git a/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.spec.ts b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.spec.ts
new file mode 100644
index 0000000000..88138a252f
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.spec.ts
@@ -0,0 +1,145 @@
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { of as observableOf } from 'rxjs';
+import { Bitstream } from '../../../core/shared/bitstream.model';
+import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
+import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
+import { FileSizePipe } from '../../../shared/utils/file-size-pipe';
+import { VarDirective } from '../../../shared/utils/var.directive';
+import { MetadataFieldWrapperComponent } from '../../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
+import { MockBitstreamFormat1 } from '../../../shared/mocks/item.mock';
+import { MediaViewerVideoComponent } from './media-viewer-video.component';
+import { By } from '@angular/platform-browser';
+
+describe('MediaViewerVideoComponent', () => {
+ let component: MediaViewerVideoComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock,
+ },
+ }),
+ BrowserAnimationsModule,
+ ],
+ declarations: [
+ MediaViewerVideoComponent,
+ VarDirective,
+ FileSizePipe,
+ MetadataFieldWrapperComponent,
+ ],
+ schemas: [NO_ERRORS_SCHEMA],
+ }).compileComponents();
+ }));
+
+ const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
+ sizeBytes: 10201,
+ content:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ format: observableOf(MockBitstreamFormat1),
+ bundleName: 'ORIGINAL',
+ _links: {
+ self: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ },
+ content: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ },
+ },
+ id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ type: 'bitstream',
+ metadata: {
+ 'dc.title': [
+ {
+ language: null,
+ value: 'test_word.docx',
+ },
+ ],
+ },
+ });
+
+ const mockMediaViewerItems: MediaViewerItem[] = Object.assign(
+ new Array(),
+ [
+ { bitstream: mockBitstream, format: 'video', thumbnail: null },
+ { bitstream: mockBitstream, format: 'video', thumbnail: null },
+ ]
+ );
+ const mockMediaViewerItem: MediaViewerItem[] = Object.assign(
+ new Array(),
+ [{ bitstream: mockBitstream, format: 'video', thumbnail: null }]
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MediaViewerVideoComponent);
+ component = fixture.componentInstance;
+ component.medias = mockMediaViewerItem;
+ component.filteredMedias = mockMediaViewerItem;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ describe('should show controller buttons when the having mode then one video', () => {
+ beforeEach(() => {
+ component.medias = mockMediaViewerItems;
+ component.filteredMedias = mockMediaViewerItems;
+ fixture.detectChanges();
+ });
+
+ it('should show buttons', () => {
+ const controllerButtons = fixture.debugElement.query(By.css('.buttons'));
+ expect(controllerButtons).toBeTruthy();
+ });
+
+ describe('when the "Next" button is clicked', () => {
+ beforeEach(() => {
+ component.currentIndex = 0;
+ fixture.detectChanges();
+ });
+
+ it('should increase the index', () => {
+ const viewMore = fixture.debugElement.query(By.css('.next'));
+ viewMore.triggerEventHandler('click', null);
+ expect(component.currentIndex).toBe(1);
+ });
+ });
+
+ describe('when the "Previous" button is clicked', () => {
+ beforeEach(() => {
+ component.currentIndex = 1;
+ fixture.detectChanges();
+ });
+
+ it('should decrease the index', () => {
+ const viewMore = fixture.debugElement.query(By.css('.previous'));
+ viewMore.triggerEventHandler('click', null);
+ expect(component.currentIndex).toBe(0);
+ });
+ });
+
+ describe('when the "Playlist element" button is clicked', () => {
+ beforeEach(() => {
+ component.isCollapsed = true;
+ fixture.detectChanges();
+ });
+
+ it('should set the the index with the selected one', () => {
+ const viewMore = fixture.debugElement.query(By.css('.list-element'));
+ viewMore.triggerEventHandler('click', null);
+ expect(component.currentIndex).toBe(0);
+ });
+ });
+ });
+});
diff --git a/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts
new file mode 100644
index 0000000000..4c578a51bb
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer-video/media-viewer-video.component.ts
@@ -0,0 +1,55 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
+
+/**
+ * This componenet renders a video viewer and playlist for the media viewer
+ */
+@Component({
+ selector: 'ds-media-viewer-video',
+ templateUrl: './media-viewer-video.component.html',
+ styleUrls: ['./media-viewer-video.component.scss'],
+})
+export class MediaViewerVideoComponent implements OnInit {
+ @Input() medias: MediaViewerItem[];
+
+ filteredMedias: MediaViewerItem[];
+
+ isCollapsed: boolean;
+ currentIndex = 0;
+
+ replacements = {
+ video: './assets/images/replacement_video.svg',
+ audio: './assets/images/replacement_audio.svg',
+ };
+
+ replacementThumbnail: string;
+
+ ngOnInit() {
+ this.isCollapsed = false;
+ this.filteredMedias = this.medias.filter(
+ (media) => media.format === 'audio' || media.format === 'video'
+ );
+ }
+
+ /**
+ * This method sets the reviced index into currentIndex
+ * @param index Selected index
+ */
+ selectedMedia(index: number) {
+ this.currentIndex = index;
+ }
+
+ /**
+ * This method increade the number of the currentIndex
+ */
+ nextMedia() {
+ this.currentIndex++;
+ }
+
+ /**
+ * This method decrese the number of the currentIndex
+ */
+ prevMedia() {
+ this.currentIndex--;
+ }
+}
diff --git a/src/app/+item-page/media-viewer/media-viewer.component.html b/src/app/+item-page/media-viewer/media-viewer.component.html
new file mode 100644
index 0000000000..b79b91629f
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer.component.html
@@ -0,0 +1,36 @@
+
+
+
+ 0">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/media-viewer/media-viewer.component.scss b/src/app/+item-page/media-viewer/media-viewer.component.scss
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer.component.scss
@@ -0,0 +1 @@
+
diff --git a/src/app/+item-page/media-viewer/media-viewer.component.spec.ts b/src/app/+item-page/media-viewer/media-viewer.component.spec.ts
new file mode 100644
index 0000000000..ebea703ec8
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer.component.spec.ts
@@ -0,0 +1,143 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Bitstream } from '../../core/shared/bitstream.model';
+import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
+import { createPaginatedList } from '../../shared/testing/utils.test';
+import { of as observableOf } from 'rxjs';
+import { By } from '@angular/platform-browser';
+import { MediaViewerComponent } from './media-viewer.component';
+import { MockBitstreamFormat1 } from '../../shared/mocks/item.mock';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { BitstreamDataService } from '../../core/data/bitstream-data.service';
+import { NO_ERRORS_SCHEMA } from '@angular/core';
+import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
+import { VarDirective } from '../../shared/utils/var.directive';
+import { MetadataFieldWrapperComponent } from '../field-components/metadata-field-wrapper/metadata-field-wrapper.component';
+import { FileSizePipe } from '../../shared/utils/file-size-pipe';
+
+describe('MediaViewerComponent', () => {
+ let comp: MediaViewerComponent;
+ let fixture: ComponentFixture;
+
+ const mockBitstream: Bitstream = Object.assign(new Bitstream(), {
+ sizeBytes: 10201,
+ content:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ format: observableOf(MockBitstreamFormat1),
+ bundleName: 'ORIGINAL',
+ _links: {
+ self: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ },
+ content: {
+ href:
+ 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
+ },
+ },
+ id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
+ type: 'bitstream',
+ metadata: {
+ 'dc.title': [
+ {
+ language: null,
+ value: 'test_word.docx',
+ },
+ ],
+ },
+ });
+
+ const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
+ findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(
+ createPaginatedList([mockBitstream])
+ ),
+ });
+
+ const mockMediaViewerItem: MediaViewerItem = Object.assign(
+ new MediaViewerItem(),
+ { bitstream: mockBitstream, format: 'image', thumbnail: null }
+ );
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock,
+ },
+ }),
+ BrowserAnimationsModule,
+ ],
+ declarations: [
+ MediaViewerComponent,
+ VarDirective,
+ FileSizePipe,
+ MetadataFieldWrapperComponent,
+ ],
+ providers: [
+ { provide: BitstreamDataService, useValue: bitstreamDataService },
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA],
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MediaViewerComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ describe('when the bitstreams are loading', () => {
+ beforeEach(() => {
+ comp.mediaList$.next([mockMediaViewerItem]);
+ comp.videoOptions = true;
+ comp.isLoading = true;
+ fixture.detectChanges();
+ });
+
+ it('should call the createMediaViewerItem', () => {
+ const mediaItem = comp.createMediaViewerItem(
+ mockBitstream,
+ MockBitstreamFormat1,
+ undefined
+ );
+ expect(mediaItem).toBeTruthy();
+ expect(mediaItem.thumbnail).toBe(null);
+ });
+
+ it('should display a loading component', () => {
+ const loading = fixture.debugElement.query(By.css('ds-loading'));
+ expect(loading.nativeElement).toBeDefined();
+ });
+ });
+
+ describe('when the bitstreams loading is failed', () => {
+ beforeEach(() => {
+ comp.mediaList$.next([]);
+ comp.videoOptions = true;
+ comp.isLoading = false;
+ fixture.detectChanges();
+ });
+
+ it('should call the createMediaViewerItem', () => {
+ const mediaItem = comp.createMediaViewerItem(
+ mockBitstream,
+ MockBitstreamFormat1,
+ undefined
+ );
+ expect(mediaItem).toBeTruthy();
+ expect(mediaItem.thumbnail).toBe(null);
+ });
+
+ it('should display a default, thumbnail', () => {
+ const defaultThumbnail = fixture.debugElement.query(
+ By.css('ds-media-viewer-image')
+ );
+ expect(defaultThumbnail.nativeElement).toBeDefined();
+ });
+ });
+});
diff --git a/src/app/+item-page/media-viewer/media-viewer.component.ts b/src/app/+item-page/media-viewer/media-viewer.component.ts
new file mode 100644
index 0000000000..3f9de8ed3e
--- /dev/null
+++ b/src/app/+item-page/media-viewer/media-viewer.component.ts
@@ -0,0 +1,114 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { filter, take } from 'rxjs/operators';
+import { BitstreamDataService } from '../../core/data/bitstream-data.service';
+import { PaginatedList } from '../../core/data/paginated-list.model';
+import { RemoteData } from '../../core/data/remote-data';
+import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
+import { Bitstream } from '../../core/shared/bitstream.model';
+import { Item } from '../../core/shared/item.model';
+import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
+import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
+import { hasValue } from '../../shared/empty.util';
+import { followLink } from '../../shared/utils/follow-link-config.model';
+
+/**
+ * This componenet renders the media viewers
+ */
+
+@Component({
+ selector: 'ds-media-viewer',
+ templateUrl: './media-viewer.component.html',
+ styleUrls: ['./media-viewer.component.scss'],
+})
+export class MediaViewerComponent implements OnInit {
+ @Input() item: Item;
+ @Input() videoOptions: boolean;
+
+ mediaList$: BehaviorSubject;
+
+ isLoading: boolean;
+
+ thumbnailPlaceholder = './assets/images/replacement_document.svg';
+
+ constructor(protected bitstreamDataService: BitstreamDataService) {}
+
+ /**
+ * This metod loads all the Bitstreams and Thumbnails and contert it to media item
+ */
+ ngOnInit(): void {
+ this.mediaList$ = new BehaviorSubject([]);
+ this.isLoading = true;
+ this.loadRemoteData('ORIGINAL').subscribe((bitstreamsRD) => {
+ if (bitstreamsRD.payload.page.length === 0) {
+ this.isLoading = false;
+ this.mediaList$.next([]);
+ } else {
+ this.loadRemoteData('THUMBNAIL').subscribe((thumbnailsRD) => {
+ for (
+ let index = 0;
+ index < bitstreamsRD.payload.page.length;
+ index++
+ ) {
+ bitstreamsRD.payload.page[index].format
+ .pipe(getFirstSucceededRemoteDataPayload())
+ .subscribe((format) => {
+ const current = this.mediaList$.getValue();
+ const mediaItem = this.createMediaViewerItem(
+ bitstreamsRD.payload.page[index],
+ format,
+ thumbnailsRD.payload && thumbnailsRD.payload.page[index]
+ );
+ this.mediaList$.next([...current, mediaItem]);
+ });
+ }
+ this.isLoading = false;
+ });
+ }
+ });
+ }
+
+ /**
+ * This method will retrieve the next page of Bitstreams from the external BitstreamDataService call.
+ * @param bundleName Bundle name
+ */
+ loadRemoteData(
+ bundleName: string
+ ): Observable>> {
+ return this.bitstreamDataService
+ .findAllByItemAndBundleName(
+ this.item,
+ bundleName,
+ {},
+ true,
+ true,
+ followLink('format')
+ )
+ .pipe(
+ filter(
+ (bitstreamsRD: RemoteData>) =>
+ hasValue(bitstreamsRD) &&
+ (hasValue(bitstreamsRD.errorMessage) || hasValue(bitstreamsRD.payload))
+ ),
+ take(1)
+ );
+ }
+
+ /**
+ * This method create MediaViewerItem from incoming bitstreams
+ * @param original original remote data bitstream
+ * @param format original bitstream format
+ * @param thumbnail trunbnail remote data bitstream
+ */
+ createMediaViewerItem(
+ original: Bitstream,
+ format: BitstreamFormat,
+ thumbnail: Bitstream
+ ): MediaViewerItem {
+ const mediaItem = new MediaViewerItem();
+ mediaItem.bitstream = original;
+ mediaItem.format = format.mimetype.split('/')[0];
+ mediaItem.thumbnail = thumbnail ? thumbnail._links.content.href : null;
+ return mediaItem;
+ }
+}
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.html b/src/app/+item-page/simple/item-types/publication/publication.component.html
index fc8d06ac10..a004712e0f 100644
--- a/src/app/+item-page/simple/item-types/publication/publication.component.html
+++ b/src/app/+item-page/simple/item-types/publication/publication.component.html
@@ -8,9 +8,14 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/simple/item-types/shared/item.component.ts b/src/app/+item-page/simple/item-types/shared/item.component.ts
index a8119c8565..120eda930f 100644
--- a/src/app/+item-page/simple/item-types/shared/item.component.ts
+++ b/src/app/+item-page/simple/item-types/shared/item.component.ts
@@ -1,5 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
+import { environment } from '../../../../../environments/environment';
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
import { Bitstream } from '../../../../core/shared/bitstream.model';
import { Item } from '../../../../core/shared/item.model';
@@ -20,6 +21,7 @@ export class ItemComponent implements OnInit {
* Route to the item page
*/
itemPageRoute: string;
+ mediaViewer = environment.mediaViewer;
constructor(protected bitstreamDataService: BitstreamDataService) {
}
diff --git a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html
index 241696f688..7a1366dda9 100644
--- a/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html
+++ b/src/app/+item-page/simple/item-types/untyped-item/untyped-item.component.html
@@ -8,9 +8,14 @@
-
-
-
+
+
+
+
+
+
+
+
diff --git a/src/app/+admin/admin-access-control/admin-access-control-routing-paths.ts b/src/app/access-control/access-control-routing-paths.ts
similarity index 56%
rename from src/app/+admin/admin-access-control/admin-access-control-routing-paths.ts
rename to src/app/access-control/access-control-routing-paths.ts
index 2080cb14a7..d229d12bd2 100644
--- a/src/app/+admin/admin-access-control/admin-access-control-routing-paths.ts
+++ b/src/app/access-control/access-control-routing-paths.ts
@@ -1,5 +1,5 @@
-import { URLCombiner } from '../../core/url-combiner/url-combiner';
-import { getAccessControlModuleRoute } from '../admin-routing-paths';
+import { URLCombiner } from '../core/url-combiner/url-combiner';
+import { getAccessControlModuleRoute } from '../app-routing-paths';
export const GROUP_EDIT_PATH = 'groups';
diff --git a/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts b/src/app/access-control/access-control-routing.module.ts
similarity index 90%
rename from src/app/+admin/admin-access-control/admin-access-control-routing.module.ts
rename to src/app/access-control/access-control-routing.module.ts
index 10ac117b0f..2dbba19f70 100644
--- a/src/app/+admin/admin-access-control/admin-access-control-routing.module.ts
+++ b/src/app/access-control/access-control-routing.module.ts
@@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router';
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
import { GroupFormComponent } from './group-registry/group-form/group-form.component';
import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
-import { GROUP_EDIT_PATH } from './admin-access-control-routing-paths';
+import { GROUP_EDIT_PATH } from './access-control-routing-paths';
@NgModule({
imports: [
@@ -26,6 +26,6 @@ import { GROUP_EDIT_PATH } from './admin-access-control-routing-paths';
/**
* Routing module for the AccessControl section of the admin sidebar
*/
-export class AdminAccessControlRoutingModule {
+export class AccessControlRoutingModule {
}
diff --git a/src/app/+admin/admin-access-control/admin-access-control.module.ts b/src/app/access-control/access-control.module.ts
similarity index 82%
rename from src/app/+admin/admin-access-control/admin-access-control.module.ts
rename to src/app/access-control/access-control.module.ts
index 04051ff46c..0e872458bd 100644
--- a/src/app/+admin/admin-access-control/admin-access-control.module.ts
+++ b/src/app/access-control/access-control.module.ts
@@ -1,8 +1,8 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
-import { SharedModule } from '../../shared/shared.module';
-import { AdminAccessControlRoutingModule } from './admin-access-control-routing.module';
+import { SharedModule } from '../shared/shared.module';
+import { AccessControlRoutingModule } from './access-control-routing.module';
import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.component';
import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component';
import { GroupFormComponent } from './group-registry/group-form/group-form.component';
@@ -15,7 +15,7 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon
CommonModule,
SharedModule,
RouterModule,
- AdminAccessControlRoutingModule
+ AccessControlRoutingModule
],
declarations: [
EPeopleRegistryComponent,
@@ -29,6 +29,6 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon
/**
* This module handles all components related to the access control pages
*/
-export class AdminAccessControlModule {
+export class AccessControlModule {
}
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.actions.ts b/src/app/access-control/epeople-registry/epeople-registry.actions.ts
similarity index 91%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.actions.ts
rename to src/app/access-control/epeople-registry/epeople-registry.actions.ts
index 000dc48263..b8b1044362 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.actions.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.actions.ts
@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
-import { EPerson } from '../../../core/eperson/models/eperson.model';
-import { type } from '../../../shared/ngrx/type';
+import { EPerson } from '../../core/eperson/models/eperson.model';
+import { type } from '../../shared/ngrx/type';
/**
* For each action type in an action group, make a simple
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html b/src/app/access-control/epeople-registry/epeople-registry.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.html
rename to src/app/access-control/epeople-registry/epeople-registry.component.html
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts
similarity index 87%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts
rename to src/app/access-control/epeople-registry/epeople-registry.component.spec.ts
index c104de0b17..2fb955bb02 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.component.spec.ts
@@ -7,24 +7,24 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
-import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../core/data/remote-data';
-import { FindListOptions } from '../../../core/data/request.models';
-import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
-import { EPerson } from '../../../core/eperson/models/eperson.model';
-import { PageInfo } from '../../../core/shared/page-info.model';
-import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
+import { RemoteData } from '../../core/data/remote-data';
+import { FindListOptions } from '../../core/data/request.models';
+import { EPersonDataService } from '../../core/eperson/eperson-data.service';
+import { EPerson } from '../../core/eperson/models/eperson.model';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { FormBuilderService } from '../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
import { EPeopleRegistryComponent } from './epeople-registry.component';
-import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
-import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
-import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock';
-import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
-import { RouterStub } from '../../../shared/testing/router.stub';
-import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
-import { RequestService } from '../../../core/data/request.service';
+import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
+import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
+import { RouterStub } from '../../shared/testing/router.stub';
+import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
+import { RequestService } from '../../core/data/request.service';
describe('EPeopleRegistryComponent', () => {
let component: EPeopleRegistryComponent;
@@ -107,7 +107,7 @@ describe('EPeopleRegistryComponent', () => {
// empty
},
getEPeoplePageRouterLink(): string {
- return '/admin/access-control/epeople';
+ return '/access-control/epeople';
}
};
authorizationService = jasmine.createSpyObj('authorizationService', {
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts b/src/app/access-control/epeople-registry/epeople-registry.component.ts
similarity index 88%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts
rename to src/app/access-control/epeople-registry/epeople-registry.component.ts
index 11b146b294..3e9b137965 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.component.ts
@@ -4,25 +4,25 @@ import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { map, switchMap, take } from 'rxjs/operators';
-import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../core/data/remote-data';
-import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
-import { EPerson } from '../../../core/eperson/models/eperson.model';
-import { hasValue } from '../../../shared/empty.util';
-import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
-import { EpersonDtoModel } from '../../../core/eperson/models/eperson-dto.model';
-import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
-import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model';
+import { RemoteData } from '../../core/data/remote-data';
+import { EPersonDataService } from '../../core/eperson/eperson-data.service';
+import { EPerson } from '../../core/eperson/models/eperson.model';
+import { hasValue } from '../../shared/empty.util';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
+import { EpersonDtoModel } from '../../core/eperson/models/eperson-dto.model';
+import { FeatureID } from '../../core/data/feature-authorization/feature-id';
+import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import {
getFirstCompletedRemoteData,
getAllSucceededRemoteData
-} from '../../../core/shared/operators';
-import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
+} from '../../core/shared/operators';
+import { ConfirmationModalComponent } from '../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-import { RequestService } from '../../../core/data/request.service';
-import { PageInfo } from '../../../core/shared/page-info.model';
-import { NoContent } from '../../../core/shared/NoContent.model';
+import { RequestService } from '../../core/data/request.service';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { NoContent } from '../../core/shared/NoContent.model';
@Component({
selector: 'ds-epeople-registry',
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts b/src/app/access-control/epeople-registry/epeople-registry.reducers.spec.ts
similarity index 96%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts
rename to src/app/access-control/epeople-registry/epeople-registry.reducers.spec.ts
index 87ab70a942..7158acc79b 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.spec.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.reducers.spec.ts
@@ -1,6 +1,6 @@
import { EPeopleRegistryCancelEPersonAction, EPeopleRegistryEditEPersonAction } from './epeople-registry.actions';
import { ePeopleRegistryReducer, EPeopleRegistryState } from './epeople-registry.reducers';
-import { EPersonMock } from '../../../shared/testing/eperson.mock';
+import { EPersonMock } from '../../shared/testing/eperson.mock';
const initialState: EPeopleRegistryState = {
editEPerson: null,
diff --git a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.ts b/src/app/access-control/epeople-registry/epeople-registry.reducers.ts
similarity index 93%
rename from src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.ts
rename to src/app/access-control/epeople-registry/epeople-registry.reducers.ts
index 42b31d61e9..1e0319f3ba 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/epeople-registry.reducers.ts
+++ b/src/app/access-control/epeople-registry/epeople-registry.reducers.ts
@@ -1,4 +1,4 @@
-import { EPerson } from '../../../core/eperson/models/eperson.model';
+import { EPerson } from '../../core/eperson/models/eperson.model';
import {
EPeopleRegistryAction,
EPeopleRegistryActionTypes,
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html
rename to src/app/access-control/epeople-registry/eperson-form/eperson-form.component.html
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
similarity index 87%
rename from src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
rename to src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
index 1163490e12..454dad0018 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
+++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
@@ -6,26 +6,26 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule, By } from '@angular/platform-browser';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
-import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../core/data/remote-data';
-import { FindListOptions } from '../../../../core/data/request.models';
-import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
-import { EPerson } from '../../../../core/eperson/models/eperson.model';
-import { PageInfo } from '../../../../core/shared/page-info.model';
-import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../core/data/remote-data';
+import { FindListOptions } from '../../../core/data/request.models';
+import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
+import { EPerson } from '../../../core/eperson/models/eperson.model';
+import { PageInfo } from '../../../core/shared/page-info.model';
+import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { EPersonFormComponent } from './eperson-form.component';
-import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
-import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
-import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
-import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
-import { AuthService } from '../../../../core/auth/auth.service';
-import { AuthServiceStub } from '../../../../shared/testing/auth-service.stub';
-import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
-import { GroupDataService } from '../../../../core/eperson/group-data.service';
-import { createPaginatedList } from '../../../../shared/testing/utils.test';
-import { RequestService } from '../../../../core/data/request.service';
+import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
+import { TranslateLoaderMock } from '../../../shared/mocks/translate-loader.mock';
+import { AuthService } from '../../../core/auth/auth.service';
+import { AuthServiceStub } from '../../../shared/testing/auth-service.stub';
+import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { GroupDataService } from '../../../core/eperson/group-data.service';
+import { createPaginatedList } from '../../../shared/testing/utils.test';
+import { RequestService } from '../../../core/data/request.service';
describe('EPersonFormComponent', () => {
let component: EPersonFormComponent;
diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts
similarity index 91%
rename from src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts
rename to src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts
index 3c284735a9..ecb4606ae4 100644
--- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts
+++ b/src/app/access-control/epeople-registry/eperson-form/eperson-form.component.ts
@@ -9,28 +9,28 @@ import {
import { TranslateService } from '@ngx-translate/core';
import { combineLatest, Observable, of, Subscription } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
-import { PaginatedList } from '../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../core/data/remote-data';
-import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../../core/eperson/group-data.service';
-import { EPerson } from '../../../../core/eperson/models/eperson.model';
-import { Group } from '../../../../core/eperson/models/group.model';
+import { PaginatedList } from '../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../core/data/remote-data';
+import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../core/eperson/group-data.service';
+import { EPerson } from '../../../core/eperson/models/eperson.model';
+import { Group } from '../../../core/eperson/models/group.model';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
-} from '../../../../core/shared/operators';
-import { hasValue } from '../../../../shared/empty.util';
-import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
-import { AuthService } from '../../../../core/auth/auth.service';
-import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
-import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
-import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component';
+} from '../../../core/shared/operators';
+import { hasValue } from '../../../shared/empty.util';
+import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
+import { AuthService } from '../../../core/auth/auth.service';
+import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
+import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
-import { RequestService } from '../../../../core/data/request.service';
-import { NoContent } from '../../../../core/shared/NoContent.model';
+import { RequestService } from '../../../core/data/request.service';
+import { NoContent } from '../../../core/shared/NoContent.model';
@Component({
selector: 'ds-eperson-form',
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.html b/src/app/access-control/group-registry/group-form/group-form.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.html
rename to src/app/access-control/group-registry/group-form/group-form.component.html
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts
similarity index 77%
rename from src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts
rename to src/app/access-control/group-registry/group-form/group-form.component.spec.ts
index fd5edf0354..d213a071d7 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts
+++ b/src/app/access-control/group-registry/group-form/group-form.component.spec.ts
@@ -9,30 +9,30 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { Store } from '@ngrx/store';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf } from 'rxjs';
-import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
-import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
-import { DSOChangeAnalyzer } from '../../../../core/data/dso-change-analyzer.service';
-import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
-import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
-import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../core/data/remote-data';
-import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../../core/eperson/group-data.service';
-import { Group } from '../../../../core/eperson/models/group.model';
-import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
-import { HALEndpointService } from '../../../../core/shared/hal-endpoint.service';
-import { PageInfo } from '../../../../core/shared/page-info.model';
-import { UUIDService } from '../../../../core/shared/uuid.service';
-import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
+import { RemoteDataBuildService } from '../../../core/cache/builders/remote-data-build.service';
+import { ObjectCacheService } from '../../../core/cache/object-cache.service';
+import { DSOChangeAnalyzer } from '../../../core/data/dso-change-analyzer.service';
+import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
+import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../core/data/remote-data';
+import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../core/eperson/group-data.service';
+import { Group } from '../../../core/eperson/models/group.model';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
+import { PageInfo } from '../../../core/shared/page-info.model';
+import { UUIDService } from '../../../core/shared/uuid.service';
+import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
import { GroupFormComponent } from './group-form.component';
-import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
-import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
-import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
-import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
-import { RouterMock } from '../../../../shared/mocks/router.mock';
-import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
+import { getMockFormBuilderService } from '../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock';
+import { RouterMock } from '../../../shared/mocks/router.mock';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
import { Operation } from 'fast-json-patch';
describe('GroupFormComponent', () => {
@@ -75,7 +75,7 @@ describe('GroupFormComponent', () => {
return observableOf(this.activeGroup);
},
getGroupRegistryRouterLink(): string {
- return '/admin/access-control/groups';
+ return '/access-control/groups';
},
editGroup(group: Group) {
this.activeGroup = group;
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.ts b/src/app/access-control/group-registry/group-form/group-form.component.ts
similarity index 89%
rename from src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.ts
rename to src/app/access-control/group-registry/group-form/group-form.component.ts
index 7984fc50d1..2b834bde1d 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.ts
+++ b/src/app/access-control/group-registry/group-form/group-form.component.ts
@@ -17,32 +17,32 @@ import {
Subscription
} from 'rxjs';
import { catchError, map, switchMap, take } from 'rxjs/operators';
-import { getCollectionEditRolesRoute } from '../../../../+collection-page/collection-page-routing-paths';
-import { getCommunityEditRolesRoute } from '../../../../+community-page/community-page-routing-paths';
-import { DSpaceObjectDataService } from '../../../../core/data/dspace-object-data.service';
-import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service';
-import { FeatureID } from '../../../../core/data/feature-authorization/feature-id';
-import { PaginatedList } from '../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../core/data/remote-data';
-import { RequestService } from '../../../../core/data/request.service';
-import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../../core/eperson/group-data.service';
-import { Group } from '../../../../core/eperson/models/group.model';
-import { Collection } from '../../../../core/shared/collection.model';
-import { Community } from '../../../../core/shared/community.model';
-import { DSpaceObject } from '../../../../core/shared/dspace-object.model';
+import { getCollectionEditRolesRoute } from '../../../+collection-page/collection-page-routing-paths';
+import { getCommunityEditRolesRoute } from '../../../+community-page/community-page-routing-paths';
+import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
+import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
+import { PaginatedList } from '../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../core/data/remote-data';
+import { RequestService } from '../../../core/data/request.service';
+import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../core/eperson/group-data.service';
+import { Group } from '../../../core/eperson/models/group.model';
+import { Collection } from '../../../core/shared/collection.model';
+import { Community } from '../../../core/shared/community.model';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
-} from '../../../../core/shared/operators';
-import { AlertType } from '../../../../shared/alert/aletr-type';
-import { ConfirmationModalComponent } from '../../../../shared/confirmation-modal/confirmation-modal.component';
-import { hasValue, isNotEmpty, hasValueOperator } from '../../../../shared/empty.util';
-import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../shared/notifications/notifications.service';
-import { followLink } from '../../../../shared/utils/follow-link-config.model';
-import { NoContent } from '../../../../core/shared/NoContent.model';
+} from '../../../core/shared/operators';
+import { AlertType } from '../../../shared/alert/aletr-type';
+import { ConfirmationModalComponent } from '../../../shared/confirmation-modal/confirmation-modal.component';
+import { hasValue, isNotEmpty, hasValueOperator } from '../../../shared/empty.util';
+import { FormBuilderService } from '../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { followLink } from '../../../shared/utils/follow-link-config.model';
+import { NoContent } from '../../../core/shared/NoContent.model';
import { Operation } from 'fast-json-patch';
@Component({
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html b/src/app/access-control/group-registry/group-form/members-list/members-list.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.html
rename to src/app/access-control/group-registry/group-form/members-list/members-list.component.html
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts
similarity index 84%
rename from src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts
rename to src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts
index 10735cbde5..20419fb49d 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts
+++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.spec.ts
@@ -7,25 +7,25 @@ import { Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf } from 'rxjs';
-import { RestResponse } from '../../../../../core/cache/response.models';
-import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../../core/data/remote-data';
-import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../../../core/eperson/group-data.service';
-import { EPerson } from '../../../../../core/eperson/models/eperson.model';
-import { Group } from '../../../../../core/eperson/models/group.model';
-import { PageInfo } from '../../../../../core/shared/page-info.model';
-import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
-import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { EPerson } from '../../../../core/eperson/models/eperson.model';
+import { Group } from '../../../../core/eperson/models/group.model';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
import { MembersListComponent } from './members-list.component';
-import { EPersonMock, EPersonMock2 } from '../../../../../shared/testing/eperson.mock';
-import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
-import { getMockTranslateService } from '../../../../../shared/mocks/translate.service.mock';
-import { getMockFormBuilderService } from '../../../../../shared/mocks/form-builder-service.mock';
-import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
-import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub';
-import { RouterMock } from '../../../../../shared/mocks/router.mock';
+import { EPersonMock, EPersonMock2 } from '../../../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
+import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
+import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
+import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
+import { RouterMock } from '../../../../shared/mocks/router.mock';
describe('MembersListComponent', () => {
let component: MembersListComponent;
@@ -66,7 +66,7 @@ describe('MembersListComponent', () => {
// empty
},
getEPeoplePageRouterLink(): string {
- return '/admin/access-control/epeople';
+ return '/access-control/epeople';
}
};
groupsDataServiceStub = {
@@ -97,7 +97,7 @@ describe('MembersListComponent', () => {
// empty
},
getGroupEditPageRouterLink(group: Group): string {
- return '/admin/access-control/groups/' + group.id;
+ return '/access-control/groups/' + group.id;
},
deleteMemberFromGroup(parentGroup, epersonToDelete: EPerson): Observable
{
this.epersonMembers = this.epersonMembers.find((eperson: EPerson) => {
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts
similarity index 93%
rename from src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts
rename to src/app/access-control/group-registry/group-form/members-list/members-list.component.ts
index c2c267b453..6513881fbf 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.ts
+++ b/src/app/access-control/group-registry/group-form/members-list/members-list.component.ts
@@ -11,21 +11,20 @@ import {
ObservedValueOf,
} from 'rxjs';
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
-import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../../core/data/remote-data';
-import { EPersonDataService } from '../../../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../../../core/eperson/group-data.service';
-import { EPerson } from '../../../../../core/eperson/models/eperson.model';
-import { Group } from '../../../../../core/eperson/models/group.model';
+import {buildPaginatedList, PaginatedList} from '../../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { EPerson } from '../../../../core/eperson/models/eperson.model';
+import { Group } from '../../../../core/eperson/models/group.model';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
- getFirstCompletedRemoteData,
- getAllCompletedRemoteData
-} from '../../../../../core/shared/operators';
-import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
-import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
-import { EpersonDtoModel } from '../../../../../core/eperson/models/eperson-dto.model';
+ getFirstCompletedRemoteData, getAllCompletedRemoteData
+} from '../../../../core/shared/operators';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
+import {EpersonDtoModel} from '../../../../core/eperson/models/eperson-dto.model';
/**
* Keys to keep track of specific subscriptions
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html
rename to src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.html
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
similarity index 86%
rename from src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
rename to src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
index 9841d2b02e..e839d77a6a 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
+++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts
@@ -15,25 +15,25 @@ import { Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf, BehaviorSubject } from 'rxjs';
-import { RestResponse } from '../../../../../core/cache/response.models';
-import { buildPaginatedList, PaginatedList } from '../../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../../core/data/remote-data';
-import { GroupDataService } from '../../../../../core/eperson/group-data.service';
-import { Group } from '../../../../../core/eperson/models/group.model';
-import { PageInfo } from '../../../../../core/shared/page-info.model';
-import { FormBuilderService } from '../../../../../shared/form/builder/form-builder.service';
-import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
-import { GroupMock, GroupMock2 } from '../../../../../shared/testing/group-mock';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { buildPaginatedList, PaginatedList } from '../../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { Group } from '../../../../core/eperson/models/group.model';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { GroupMock, GroupMock2 } from '../../../../shared/testing/group-mock';
import { SubgroupsListComponent } from './subgroups-list.component';
import {
createSuccessfulRemoteDataObject$,
createSuccessfulRemoteDataObject
-} from '../../../../../shared/remote-data.utils';
-import { RouterMock } from '../../../../../shared/mocks/router.mock';
-import { getMockFormBuilderService } from '../../../../../shared/mocks/form-builder-service.mock';
-import { getMockTranslateService } from '../../../../../shared/mocks/translate.service.mock';
-import { TranslateLoaderMock } from '../../../../../shared/testing/translate-loader.mock';
-import { NotificationsServiceStub } from '../../../../../shared/testing/notifications-service.stub';
+} from '../../../../shared/remote-data.utils';
+import { RouterMock } from '../../../../shared/mocks/router.mock';
+import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder-service.mock';
+import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
+import { TranslateLoaderMock } from '../../../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { map } from 'rxjs/operators';
describe('SubgroupsListComponent', () => {
@@ -70,7 +70,7 @@ describe('SubgroupsListComponent', () => {
);
},
getGroupEditPageRouterLink(group: Group): string {
- return '/admin/access-control/groups/' + group.id;
+ return '/access-control/groups/' + group.id;
},
searchGroups(query: string): Observable>> {
if (query === '') {
diff --git a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts
similarity index 93%
rename from src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts
rename to src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts
index d754e71e4f..d9f03963d6 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts
+++ b/src/app/access-control/group-registry/group-form/subgroup-list/subgroups-list.component.ts
@@ -4,18 +4,18 @@ import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Observable, of as observableOf, Subscription, BehaviorSubject } from 'rxjs';
import { map, mergeMap, take } from 'rxjs/operators';
-import { PaginatedList } from '../../../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../../../core/data/remote-data';
-import { GroupDataService } from '../../../../../core/eperson/group-data.service';
-import { Group } from '../../../../../core/eperson/models/group.model';
+import { PaginatedList } from '../../../../core/data/paginated-list.model';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { GroupDataService } from '../../../../core/eperson/group-data.service';
+import { Group } from '../../../../core/eperson/models/group.model';
import {
getRemoteDataPayload,
getFirstSucceededRemoteData,
getFirstCompletedRemoteData
-} from '../../../../../core/shared/operators';
-import { NotificationsService } from '../../../../../shared/notifications/notifications.service';
-import { PaginationComponentOptions } from '../../../../../shared/pagination/pagination-component-options.model';
-import { NoContent } from '../../../../../core/shared/NoContent.model';
+} from '../../../../core/shared/operators';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
+import { NoContent } from '../../../../core/shared/NoContent.model';
/**
* Keys to keep track of specific subscriptions
diff --git a/src/app/+admin/admin-access-control/group-registry/group-registry.actions.ts b/src/app/access-control/group-registry/group-registry.actions.ts
similarity index 91%
rename from src/app/+admin/admin-access-control/group-registry/group-registry.actions.ts
rename to src/app/access-control/group-registry/group-registry.actions.ts
index b5ba3bc4b5..bc1c0b97a6 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-registry.actions.ts
+++ b/src/app/access-control/group-registry/group-registry.actions.ts
@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
-import { Group } from '../../../core/eperson/models/group.model';
-import { type } from '../../../shared/ngrx/type';
+import { Group } from '../../core/eperson/models/group.model';
+import { type } from '../../shared/ngrx/type';
/**
* For each action type in an action group, make a simple
diff --git a/src/app/+admin/admin-access-control/group-registry/group-registry.reducers.spec.ts b/src/app/access-control/group-registry/group-registry.reducers.spec.ts
similarity index 96%
rename from src/app/+admin/admin-access-control/group-registry/group-registry.reducers.spec.ts
rename to src/app/access-control/group-registry/group-registry.reducers.spec.ts
index 6c9f9d327a..de5b65f5ba 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-registry.reducers.spec.ts
+++ b/src/app/access-control/group-registry/group-registry.reducers.spec.ts
@@ -1,4 +1,4 @@
-import { GroupMock } from '../../../shared/testing/group-mock';
+import { GroupMock } from '../../shared/testing/group-mock';
import { GroupRegistryCancelGroupAction, GroupRegistryEditGroupAction } from './group-registry.actions';
import { groupRegistryReducer, GroupRegistryState } from './group-registry.reducers';
diff --git a/src/app/+admin/admin-access-control/group-registry/group-registry.reducers.ts b/src/app/access-control/group-registry/group-registry.reducers.ts
similarity index 93%
rename from src/app/+admin/admin-access-control/group-registry/group-registry.reducers.ts
rename to src/app/access-control/group-registry/group-registry.reducers.ts
index eca6c282a7..8e288b7f3a 100644
--- a/src/app/+admin/admin-access-control/group-registry/group-registry.reducers.ts
+++ b/src/app/access-control/group-registry/group-registry.reducers.ts
@@ -1,4 +1,4 @@
-import { Group } from '../../../core/eperson/models/group.model';
+import { Group } from '../../core/eperson/models/group.model';
import { GroupRegistryAction, GroupRegistryActionTypes, GroupRegistryEditGroupAction } from './group-registry.actions';
/**
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.html b/src/app/access-control/group-registry/groups-registry.component.html
similarity index 100%
rename from src/app/+admin/admin-access-control/group-registry/groups-registry.component.html
rename to src/app/access-control/group-registry/groups-registry.component.html
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts b/src/app/access-control/group-registry/groups-registry.component.spec.ts
similarity index 80%
rename from src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts
rename to src/app/access-control/group-registry/groups-registry.component.spec.ts
index dd08ea6772..b5b5a1b209 100644
--- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.spec.ts
+++ b/src/app/access-control/group-registry/groups-registry.component.spec.ts
@@ -7,27 +7,27 @@ import { Router } from '@angular/router';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { Observable, of as observableOf } from 'rxjs';
-import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
-import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
-import { buildPaginatedList, PaginatedList } from '../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../core/data/remote-data';
-import { RequestService } from '../../../core/data/request.service';
-import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../core/eperson/group-data.service';
-import { EPerson } from '../../../core/eperson/models/eperson.model';
-import { Group } from '../../../core/eperson/models/group.model';
-import { RouteService } from '../../../core/services/route.service';
-import { DSpaceObject } from '../../../core/shared/dspace-object.model';
-import { PageInfo } from '../../../core/shared/page-info.model';
-import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { GroupMock, GroupMock2 } from '../../../shared/testing/group-mock';
+import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
+import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
+import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
+import { RemoteData } from '../../core/data/remote-data';
+import { RequestService } from '../../core/data/request.service';
+import { EPersonDataService } from '../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../core/eperson/group-data.service';
+import { EPerson } from '../../core/eperson/models/eperson.model';
+import { Group } from '../../core/eperson/models/group.model';
+import { RouteService } from '../../core/services/route.service';
+import { DSpaceObject } from '../../core/shared/dspace-object.model';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
import { GroupsRegistryComponent } from './groups-registry.component';
-import { EPersonMock, EPersonMock2 } from '../../../shared/testing/eperson.mock';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
-import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mock';
-import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
-import { routeServiceStub } from '../../../shared/testing/route-service.stub';
-import { RouterMock } from '../../../shared/mocks/router.mock';
+import { EPersonMock, EPersonMock2 } from '../../shared/testing/eperson.mock';
+import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
+import { TranslateLoaderMock } from '../../shared/testing/translate-loader.mock';
+import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
+import { routeServiceStub } from '../../shared/testing/route-service.stub';
+import { RouterMock } from '../../shared/mocks/router.mock';
describe('GroupRegistryComponent', () => {
let component: GroupsRegistryComponent;
@@ -98,10 +98,10 @@ describe('GroupRegistryComponent', () => {
}
},
getGroupEditPageRouterLink(group: Group): string {
- return '/admin/access-control/groups/' + group.id;
+ return '/access-control/groups/' + group.id;
},
getGroupRegistryRouterLink(): string {
- return '/admin/access-control/groups';
+ return '/access-control/groups';
},
searchGroups(query: string): Observable>> {
if (query === '') {
diff --git a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts b/src/app/access-control/group-registry/groups-registry.component.ts
similarity index 84%
rename from src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts
rename to src/app/access-control/group-registry/groups-registry.component.ts
index 305da75eeb..bfe6d50c43 100644
--- a/src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts
+++ b/src/app/access-control/group-registry/groups-registry.component.ts
@@ -10,29 +10,29 @@ import {
of as observableOf
} from 'rxjs';
import { catchError, map, switchMap, take } from 'rxjs/operators';
-import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
-import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
-import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
-import { PaginatedList, buildPaginatedList } from '../../../core/data/paginated-list.model';
-import { RemoteData } from '../../../core/data/remote-data';
-import { RequestService } from '../../../core/data/request.service';
-import { EPersonDataService } from '../../../core/eperson/eperson-data.service';
-import { GroupDataService } from '../../../core/eperson/group-data.service';
-import { EPerson } from '../../../core/eperson/models/eperson.model';
-import { GroupDtoModel } from '../../../core/eperson/models/group-dto.model';
-import { Group } from '../../../core/eperson/models/group.model';
-import { RouteService } from '../../../core/services/route.service';
-import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
+import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
+import { FeatureID } from '../../core/data/feature-authorization/feature-id';
+import { PaginatedList, buildPaginatedList } from '../../core/data/paginated-list.model';
+import { RemoteData } from '../../core/data/remote-data';
+import { RequestService } from '../../core/data/request.service';
+import { EPersonDataService } from '../../core/eperson/eperson-data.service';
+import { GroupDataService } from '../../core/eperson/group-data.service';
+import { EPerson } from '../../core/eperson/models/eperson.model';
+import { GroupDtoModel } from '../../core/eperson/models/group-dto.model';
+import { Group } from '../../core/eperson/models/group.model';
+import { RouteService } from '../../core/services/route.service';
+import { DSpaceObject } from '../../core/shared/dspace-object.model';
import {
getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData,
getFirstSucceededRemoteData
-} from '../../../core/shared/operators';
-import { PageInfo } from '../../../core/shared/page-info.model';
-import { hasValue } from '../../../shared/empty.util';
-import { NotificationsService } from '../../../shared/notifications/notifications.service';
-import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
-import { NoContent } from '../../../core/shared/NoContent.model';
+} from '../../core/shared/operators';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { hasValue } from '../../shared/empty.util';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
+import { NoContent } from '../../core/shared/NoContent.model';
@Component({
selector: 'ds-groups-registry',
diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts
index 24108dadd5..08f7b9585f 100644
--- a/src/app/app-routing-paths.ts
+++ b/src/app/app-routing-paths.ts
@@ -74,3 +74,9 @@ export const INFO_MODULE_PATH = 'info';
export function getInfoModulePath() {
return `/${INFO_MODULE_PATH}`;
}
+
+export const ACCESS_CONTROL_MODULE_PATH = 'access-control';
+
+export function getAccessControlModuleRoute() {
+ return `/${ACCESS_CONTROL_MODULE_PATH}`;
+}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 13e1133d82..ffbd993e8c 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -4,7 +4,17 @@ import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard';
-import { ADMIN_MODULE_PATH, BITSTREAM_MODULE_PATH, FORBIDDEN_PATH, FORGOT_PASSWORD_PATH, INFO_MODULE_PATH, PROFILE_MODULE_PATH, REGISTER_PATH, WORKFLOW_ITEM_MODULE_PATH } from './app-routing-paths';
+import {
+ ACCESS_CONTROL_MODULE_PATH,
+ ADMIN_MODULE_PATH,
+ BITSTREAM_MODULE_PATH,
+ FORBIDDEN_PATH,
+ FORGOT_PASSWORD_PATH,
+ INFO_MODULE_PATH,
+ PROFILE_MODULE_PATH,
+ REGISTER_PATH,
+ WORKFLOW_ITEM_MODULE_PATH,
+} from './app-routing-paths';
import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths';
import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths';
import { ITEM_MODULE_PATH } from './+item-page/item-page-routing-paths';
@@ -14,6 +24,7 @@ import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
import { ThemedPageNotFoundComponent } from './pagenotfound/themed-pagenotfound.component';
import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component';
+import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard';
@NgModule({
imports: [
@@ -171,6 +182,11 @@ import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component
loadChildren: () => import('./statistics-page/statistics-page-routing.module')
.then((m) => m.StatisticsPageRoutingModule),
},
+ {
+ path: ACCESS_CONTROL_MODULE_PATH,
+ loadChildren: () => import('./access-control/access-control.module').then((m) => m.AccessControlModule),
+ canActivate: [GroupAdministratorGuard],
+ },
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
]}
],{
diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts
index 5dffda5e94..5abc378702 100644
--- a/src/app/app.reducer.ts
+++ b/src/app/app.reducer.ts
@@ -3,11 +3,11 @@ import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store'
import {
ePeopleRegistryReducer,
EPeopleRegistryState
-} from './+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
+} from './access-control/epeople-registry/epeople-registry.reducers';
import {
groupRegistryReducer,
GroupRegistryState
-} from './+admin/admin-access-control/group-registry/group-registry.reducers';
+} from './access-control/group-registry/group-registry.reducers';
import {
metadataRegistryReducer,
MetadataRegistryState
diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts
index 31de304665..7b9a08de92 100644
--- a/src/app/core/auth/auth.interceptor.ts
+++ b/src/app/core/auth/auth.interceptor.ts
@@ -102,7 +102,7 @@ export class AuthInterceptor implements HttpInterceptor {
private parseLocation(header: string): string {
let location = header.trim();
location = location.replace('location="', '');
- location = location.replace('"', '');
+ location = location.replace('"', ''); /* lgtm [js/incomplete-sanitization] */
let re = /%3A%2F%2F/g;
location = location.replace(re, '://');
re = /%3A/g;
diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts
new file mode 100644
index 0000000000..bc39397ed9
--- /dev/null
+++ b/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { FeatureAuthorizationGuard } from './feature-authorization.guard';
+import { AuthorizationDataService } from '../authorization-data.service';
+import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
+import { AuthService } from '../../../auth/auth.service';
+import { Observable, of as observableOf } from 'rxjs';
+import { FeatureID } from '../feature-id';
+
+/**
+ * Prevent unauthorized activating and loading of routes when the current authenticated user
+ * isn't a Collection administrator
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class CollectionAdministratorGuard extends FeatureAuthorizationGuard {
+ constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
+ super(authorizationService, router, authService);
+ }
+
+ /**
+ * Check group management rights
+ */
+ getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return observableOf(FeatureID.IsCollectionAdmin);
+ }
+}
diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts
new file mode 100644
index 0000000000..afb1fea63d
--- /dev/null
+++ b/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { FeatureAuthorizationGuard } from './feature-authorization.guard';
+import { AuthorizationDataService } from '../authorization-data.service';
+import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
+import { AuthService } from '../../../auth/auth.service';
+import { Observable, of as observableOf } from 'rxjs';
+import { FeatureID } from '../feature-id';
+
+/**
+ * Prevent unauthorized activating and loading of routes when the current authenticated user
+ * isn't a Community administrator
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class CommunityAdministratorGuard extends FeatureAuthorizationGuard {
+ constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
+ super(authorizationService, router, authService);
+ }
+
+ /**
+ * Check group management rights
+ */
+ getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return observableOf(FeatureID.IsCommunityAdmin);
+ }
+}
diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts
new file mode 100644
index 0000000000..3fee767fdc
--- /dev/null
+++ b/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts
@@ -0,0 +1,27 @@
+import { Injectable } from '@angular/core';
+import { FeatureAuthorizationGuard } from './feature-authorization.guard';
+import { AuthorizationDataService } from '../authorization-data.service';
+import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
+import { AuthService } from '../../../auth/auth.service';
+import { Observable, of as observableOf } from 'rxjs';
+import { FeatureID } from '../feature-id';
+
+/**
+ * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group
+ * management rights
+ */
+@Injectable({
+ providedIn: 'root'
+})
+export class GroupAdministratorGuard extends FeatureAuthorizationGuard {
+ constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) {
+ super(authorizationService, router, authService);
+ }
+
+ /**
+ * Check group management rights
+ */
+ getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
+ return observableOf(FeatureID.CanManageGroups);
+ }
+}
diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts
index 3c17f43cb1..e3473a895e 100644
--- a/src/app/core/data/feature-authorization/feature-id.ts
+++ b/src/app/core/data/feature-authorization/feature-id.ts
@@ -9,4 +9,7 @@ export enum FeatureID {
WithdrawItem = 'withdrawItem',
ReinstateItem = 'reinstateItem',
EPersonRegistration = 'epersonRegistration',
+ CanManageGroups = 'canManageGroups',
+ IsCollectionAdmin = 'isCollectionAdmin',
+ IsCommunityAdmin = 'isCommunityAdmin',
}
diff --git a/src/app/core/eperson/eperson-data.service.spec.ts b/src/app/core/eperson/eperson-data.service.spec.ts
index 3af02b267b..cd7b664379 100644
--- a/src/app/core/eperson/eperson-data.service.spec.ts
+++ b/src/app/core/eperson/eperson-data.service.spec.ts
@@ -10,7 +10,7 @@ import { TestScheduler } from 'rxjs/testing';
import {
EPeopleRegistryCancelEPersonAction,
EPeopleRegistryEditEPersonAction
-} from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
+} from '../../access-control/epeople-registry/epeople-registry.actions';
import { RequestParam } from '../cache/models/request-param.model';
import { CoreState } from '../core.reducers';
import { ChangeAnalyzer } from '../data/change-analyzer';
diff --git a/src/app/core/eperson/eperson-data.service.ts b/src/app/core/eperson/eperson-data.service.ts
index 79df246833..bceb38f163 100644
--- a/src/app/core/eperson/eperson-data.service.ts
+++ b/src/app/core/eperson/eperson-data.service.ts
@@ -7,8 +7,8 @@ import { find, map, take } from 'rxjs/operators';
import {
EPeopleRegistryCancelEPersonAction,
EPeopleRegistryEditEPersonAction
-} from '../../+admin/admin-access-control/epeople-registry/epeople-registry.actions';
-import { EPeopleRegistryState } from '../../+admin/admin-access-control/epeople-registry/epeople-registry.reducers';
+} from '../../access-control/epeople-registry/epeople-registry.actions';
+import { EPeopleRegistryState } from '../../access-control/epeople-registry/epeople-registry.reducers';
import { AppState } from '../../app.reducer';
import { hasValue, hasNoValue } from '../../shared/empty.util';
import { NotificationsService } from '../../shared/notifications/notifications.service';
@@ -269,7 +269,7 @@ export class EPersonDataService extends DataService {
this.editEPerson(ePerson);
}
});
- return '/admin/access-control/epeople';
+ return '/access-control/epeople';
}
/**
@@ -277,7 +277,7 @@ export class EPersonDataService extends DataService {
* @param ePerson New EPerson to edit
*/
public getEPeoplePageRouterLink(): string {
- return '/admin/access-control/epeople';
+ return '/access-control/epeople';
}
/**
diff --git a/src/app/core/eperson/group-data.service.spec.ts b/src/app/core/eperson/group-data.service.spec.ts
index f77c831b55..378c3c9667 100644
--- a/src/app/core/eperson/group-data.service.spec.ts
+++ b/src/app/core/eperson/group-data.service.spec.ts
@@ -8,7 +8,7 @@ import { compare, Operation } from 'fast-json-patch';
import {
GroupRegistryCancelGroupAction,
GroupRegistryEditGroupAction
-} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
+} from '../../access-control/group-registry/group-registry.actions';
import { GroupMock, GroupMock2 } from '../../shared/testing/group-mock';
import { RequestParam } from '../cache/models/request-param.model';
import { CoreState } from '../core.reducers';
diff --git a/src/app/core/eperson/group-data.service.ts b/src/app/core/eperson/group-data.service.ts
index dc5fd97d9a..5b8f474d1a 100644
--- a/src/app/core/eperson/group-data.service.ts
+++ b/src/app/core/eperson/group-data.service.ts
@@ -7,8 +7,8 @@ import { filter, map, take } from 'rxjs/operators';
import {
GroupRegistryCancelGroupAction,
GroupRegistryEditGroupAction
-} from '../../+admin/admin-access-control/group-registry/group-registry.actions';
-import { GroupRegistryState } from '../../+admin/admin-access-control/group-registry/group-registry.reducers';
+} from '../../access-control/group-registry/group-registry.actions';
+import { GroupRegistryState } from '../../access-control/group-registry/group-registry.reducers';
import { AppState } from '../../app.reducer';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
@@ -209,7 +209,7 @@ export class GroupDataService extends DataService {
}
public getGroupRegistryRouterLink(): string {
- return '/admin/access-control/groups';
+ return '/access-control/groups';
}
/**
@@ -240,7 +240,7 @@ export class GroupDataService extends DataService {
* @param groupID Group ID we want edit page for
*/
public getGroupEditPageRouterLinkWithID(groupId: string): string {
- return '/admin/access-control/groups/' + groupId;
+ return '/access-control/groups/' + groupId;
}
/**
diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts
index fdfe1c9fac..5f2f123f01 100644
--- a/src/app/core/registry/registry.service.spec.ts
+++ b/src/app/core/registry/registry.service.spec.ts
@@ -326,6 +326,25 @@ describe('RegistryService', () => {
});
});
+ describe('when createMetadataField is called with a blank qualifier', () => {
+ let result: Observable;
+ let metadataField: MetadataField;
+
+ beforeEach(() => {
+ metadataField = mockFieldsList[0];
+ metadataField.qualifier = '';
+ result = registryService.createMetadataField(metadataField, mockSchemasList[0]);
+ });
+
+ it('should return the created metadata field with a null qualifier', (done) => {
+ metadataField.qualifier = null;
+ result.subscribe((field: MetadataField) => {
+ expect(field).toEqual(metadataField);
+ done();
+ });
+ });
+ });
+
describe('when updateMetadataField is called', () => {
let result: Observable;
@@ -341,6 +360,25 @@ describe('RegistryService', () => {
});
});
+ describe('when updateMetadataField is called with a blank qualifier', () => {
+ let result: Observable;
+ let metadataField: MetadataField;
+
+ beforeEach(() => {
+ metadataField = mockFieldsList[0];
+ metadataField.qualifier = '';
+ result = registryService.updateMetadataField(metadataField);
+ });
+
+ it('should return the updated metadata field with a null qualifier', (done) => {
+ metadataField.qualifier = null;
+ result.subscribe((field: MetadataField) => {
+ expect(field).toEqual(metadataField);
+ done();
+ });
+ });
+ });
+
describe('when deleteMetadataSchema is called', () => {
let result: Observable>;
diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts
index 9ac849bdd3..b7b35c6a5a 100644
--- a/src/app/core/registry/registry.service.ts
+++ b/src/app/core/registry/registry.service.ts
@@ -245,6 +245,9 @@ export class RegistryService {
* @param schema The MetadataSchema to create the field in
*/
public createMetadataField(field: MetadataField, schema: MetadataSchema): Observable {
+ if (!field.qualifier) {
+ field.qualifier = null;
+ }
return this.metadataFieldService.create(field, new RequestParam('schemaId', schema.id)).pipe(
getFirstSucceededRemoteDataPayload(),
hasValueOperator(),
@@ -260,6 +263,9 @@ export class RegistryService {
* @param field The MetadataField to update
*/
public updateMetadataField(field: MetadataField): Observable {
+ if (!field.qualifier) {
+ field.qualifier = null;
+ }
return this.metadataFieldService.put(field).pipe(
getFirstSucceededRemoteDataPayload(),
hasValueOperator(),
diff --git a/src/app/core/shared/media-viewer-item.model.ts b/src/app/core/shared/media-viewer-item.model.ts
new file mode 100644
index 0000000000..cd3a31bd0b
--- /dev/null
+++ b/src/app/core/shared/media-viewer-item.model.ts
@@ -0,0 +1,21 @@
+import { Bitstream } from './bitstream.model';
+
+/**
+ * Model representing a media viewer item
+ */
+export class MediaViewerItem {
+ /**
+ * Incoming Bitsream
+ */
+ bitstream: Bitstream;
+
+ /**
+ * Incoming Bitsream format type
+ */
+ format: string;
+
+ /**
+ * Incoming Bitsream thumbnail
+ */
+ thumbnail: string;
+}
diff --git a/src/app/core/shared/metadata.utils.spec.ts b/src/app/core/shared/metadata.utils.spec.ts
index ec0b3dd3ba..bc91d0585e 100644
--- a/src/app/core/shared/metadata.utils.spec.ts
+++ b/src/app/core/shared/metadata.utils.spec.ts
@@ -20,6 +20,7 @@ const dcTitle0 = mdValue('Title 0');
const dcTitle1 = mdValue('Title 1');
const dcTitle2 = mdValue('Title 2', 'en_US');
const bar = mdValue('Bar');
+const test = mdValue('Test');
const singleMap = { 'dc.title': [dcTitle0] };
@@ -30,6 +31,11 @@ const multiMap = {
'foo': [bar]
};
+const regexTestMap = {
+ 'foolbar.baz': [test],
+ 'foo.bard': [test],
+};
+
const multiViewModelList = [
{ key: 'dc.description', ...dcDescription, order: 0 },
{ key: 'dc.description.abstract', ...dcAbstract, order: 0 },
@@ -98,6 +104,9 @@ describe('Metadata', () => {
testAll([multiMap, singleMap], 'dc.*', [dcDescription, dcAbstract, dcTitle1, dcTitle2]);
testAll([multiMap, singleMap], ['dc.title', 'dc.*'], [dcTitle1, dcTitle2, dcDescription, dcAbstract]);
});
+ describe('with regexTestMap', () => {
+ testAll(regexTestMap, 'foo.bar.*', []);
+ });
});
describe('allValues method', () => {
diff --git a/src/app/core/shared/metadata.utils.ts b/src/app/core/shared/metadata.utils.ts
index 612fba1d4a..3fbeb205d2 100644
--- a/src/app/core/shared/metadata.utils.ts
+++ b/src/app/core/shared/metadata.utils.ts
@@ -156,7 +156,7 @@ export class Metadata {
const outputKeys: string[] = [];
for (const inputKey of inputKeys) {
if (inputKey.includes('*')) {
- const inputKeyRegex = new RegExp('^' + inputKey.replace('.', '\.').replace('*', '.*') + '$');
+ const inputKeyRegex = new RegExp('^' + inputKey.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
for (const mapKey of Object.keys(mdMap)) {
if (!outputKeys.includes(mapKey) && inputKeyRegex.test(mapKey)) {
outputKeys.push(mapKey);
diff --git a/src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts b/src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
index 7ec422e69d..d155d66a0a 100644
--- a/src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
+++ b/src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts
@@ -9,7 +9,7 @@ import { getAllCompletedRemoteData, getFirstCompletedRemoteData } from '../../..
import { RequestService } from '../../../../core/data/request.service';
import { RemoteData } from '../../../../core/data/remote-data';
import { HALLink } from '../../../../core/shared/hal-link.model';
-import { getGroupEditRoute } from '../../../../+admin/admin-access-control/admin-access-control-routing-paths';
+import { getGroupEditRoute } from '../../../../access-control/access-control-routing-paths';
import { hasNoValue, hasValue } from '../../../empty.util';
import { NoContent } from '../../../../core/shared/NoContent.model';
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
index 7473f894fa..bfa9c214e9 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
@@ -11,12 +11,15 @@
'd-none': value?.isVirtual && (model.hasSelectableMetadata || context?.index > 0)}">
-
+
+
+
-
- {{ message | translate: model.validators }}
-
+
+ {{ message | translate: model.validators }}
+
0" class="col-xs-2" >
@@ -32,12 +35,12 @@
- 0">
+
@@ -65,6 +68,9 @@
[relationshipOptions]="model.relationship"
>
+
+
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
index 014c25e189..fd403561e9 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts
@@ -37,7 +37,6 @@ import {
DynamicFormControl,
DynamicFormControlContainerComponent,
DynamicFormControlEvent,
- DynamicFormControlEventType,
DynamicFormControlModel,
DynamicFormLayout,
DynamicFormLayoutService,
@@ -91,10 +90,10 @@ import { DYNAMIC_FORM_CONTROL_TYPE_DISABLED } from './models/disabled/dynamic-di
import { DsDynamicLookupRelationModalComponent } from './relation-lookup-modal/dynamic-lookup-relation-modal.component';
import {
getAllSucceededRemoteData,
+ getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getPaginatedListPayload,
- getRemoteDataPayload,
- getFirstSucceededRemoteData
+ getRemoteDataPayload
} from '../../../../core/shared/operators';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';
@@ -374,6 +373,15 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
}
+ hasRelationship() {
+ return isNotEmpty(this.model) && this.model.hasOwnProperty('relationship') && isNotEmpty(this.model.relationship);
+ }
+
+ isVirtual() {
+ const value: FormFieldMetadataValueObject = this.model.metadataValue;
+ return isNotEmpty(value) && value.isVirtual;
+ }
+
public hasResultsSelected(): Observable
{
return this.model.value.pipe(map((list: SearchResult[]) => isNotEmpty(list)));
}
@@ -385,6 +393,11 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
this.modalRef = this.modalService.open(DsDynamicLookupRelationModalComponent, {
size: 'lg'
});
+
+ if (hasValue(this.model.value)) {
+ this.submissionService.dispatchSave(this.model.submissionId);
+ }
+
const modalComp = this.modalRef.componentInstance;
if (hasValue(this.model.value) && !this.model.readOnly) {
@@ -395,18 +408,6 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
}
}
- if (hasValue(this.model.value)) {
- this.model.value = '';
- this.onChange({
- $event: { previousIndex: 0 },
- context: { index: 0 },
- control: this.control,
- model: this.model,
- type: DynamicFormControlEventType.Change
- });
- }
- this.submissionService.dispatchSave(this.model.submissionId);
-
modalComp.repeatable = this.model.repeatable;
modalComp.listId = this.listId;
modalComp.relationshipOptions = this.model.relationship;
@@ -437,6 +438,10 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo
.forEach((sub) => sub.unsubscribe());
}
+ get hasHint(): boolean {
+ return isNotEmpty(this.model.hint) && this.model.hint !== ' ';
+ }
+
/**
* Initialize this.item$ based on this.model.submissionId
*/
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html
index 57ab7d66d8..07ea131a00 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html
@@ -8,6 +8,7 @@
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
index c606145c03..3a5623cfdd 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts
@@ -14,6 +14,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils
import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
import { of as observableOf } from 'rxjs';
+import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
+import { TranslateLoaderMock } from '../../../../testing/translate-loader.mock';
describe('ExistingMetadataListElementComponent', () => {
let component: ExistingMetadataListElementComponent;
@@ -65,6 +67,14 @@ describe('ExistingMetadataListElementComponent', () => {
beforeEach(waitForAsync(() => {
init();
TestBed.configureTestingModule({
+ imports: [
+ TranslateModule.forRoot({
+ loader: {
+ provide: TranslateLoader,
+ useClass: TranslateLoaderMock
+ }
+ })
+ ],
declarations: [ExistingMetadataListElementComponent],
providers: [
{ provide: SelectableListService, useValue: selectionService },
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
index 7cd153ef5c..fe0c65ea73 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.html
@@ -2,44 +2,42 @@
-
-
-
-
-
-
-
0}" cdkDrag cdkDragHandle>
-
0}"
- >
- 0">
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss
index 8fb13f9c55..634a197123 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.scss
@@ -5,48 +5,41 @@
}
.cdk-drag {
- margin-left: calc(-2 * var(--bs-spacer));
- margin-right: calc(-0.5 * var(--bs-spacer));
- padding-right: calc(0.5 * var(--bs-spacer));
- .drag-icon {
- visibility: hidden;
- width: calc(2 * var(--bs-spacer));
- color: var(--bs-gray-600);
- margin: var(--bs-btn-padding-y) 0;
- line-height: var(--bs-btn-line-height);
- text-indent: calc(0.5 * var(--bs-spacer))
- }
+ margin-left: calc(-2.3 * var(--bs-spacer));
+ margin-right: calc(-0.5 * var(--bs-spacer));
+ padding-right: calc(0.5 * var(--bs-spacer));
+ .drag-icon {
+ visibility: hidden;
+ width: calc(2 * var(--bs-spacer));
+ color: var(--bs-gray-600);
+ margin: var(--bs-btn-padding-y) 0;
+ line-height: var(--bs-btn-line-height);
+ text-indent: calc(0.5 * var(--bs-spacer))
+ }
- &:hover, &:focus {
- cursor: grab;
- .drag-icon {
- visibility: visible;
- }
+ &:hover, &:focus {
+ cursor: grab;
+ .drag-icon {
+ visibility: visible;
}
+ }
}
.cdk-drop-list-dragging {
- .cdk-drag {
- cursor: grabbing;
- .drag-icon {
- visibility: hidden;
- }
+ .cdk-drag {
+ cursor: grabbing;
+ .drag-icon {
+ visibility: hidden;
}
+ }
}
.cdk-drag-preview {
- background-color: white;
- border-radius: var(--bs-border-radius-sm);
- margin-left: 0;
- box-shadow: 0 5px 5px 0px rgba(0, 0, 0, 0.2),
- 0 8px 10px 1px rgba(0, 0, 0, 0.14),
- 0 3px 14px 2px rgba(0, 0, 0, 0.12);
- .drag-icon {
- visibility: visible;
- }
+ margin: 0;
+ padding: 0;
}
.cdk-drag-placeholder {
- opacity: 0;
+ opacity: 0;
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
index 80b6e000b8..8ab38454a7 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/array-group/dynamic-form-array.component.ts
@@ -5,15 +5,15 @@ import {
DynamicFormArrayComponent,
DynamicFormControlCustomEvent,
DynamicFormControlEvent,
- DynamicFormControlEventType,
- DynamicFormControlLayout, DynamicFormLayout,
+ DynamicFormControlLayout,
+ DynamicFormLayout,
DynamicFormLayoutService,
DynamicFormValidationService,
DynamicTemplateDirective
} from '@ng-dynamic-forms/core';
import { Relationship } from '../../../../../../core/shared/item-relationships/relationship.model';
-import { DynamicRowArrayModel } from '../ds-dynamic-row-array-model';
import { hasValue } from '../../../../../empty.util';
+import { DynamicRowArrayModel } from '../ds-dynamic-row-array-model';
@Component({
selector: 'ds-dynamic-form-array',
@@ -25,7 +25,7 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
@Input() formLayout: DynamicFormLayout;
@Input() group: FormGroup;
@Input() layout: DynamicFormControlLayout;
- @Input() model: DynamicRowArrayModel;
+ @Input() model: DynamicRowArrayModel;// DynamicRow?
@Input() templates: QueryList
| undefined;
/* tslint:disable:no-output-rename */
@@ -43,22 +43,25 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
}
moveSelection(event: CdkDragDrop) {
+
+ // prevent propagating events generated releasing on the same position
+ if (event.previousIndex === event.currentIndex) {
+ return;
+ }
+
this.model.moveGroup(event.previousIndex, event.currentIndex - event.previousIndex);
- const prevIndex = event.previousIndex - 1;
- const index = event.currentIndex - 1;
+ const prevIndex = event.previousIndex;
+ const index = event.currentIndex;
if (hasValue(this.model.groups[index]) && hasValue((this.control as any).controls[index])) {
- const $event = {
- $event: { previousIndex: prevIndex },
- context: { index },
- control: (this.control as any).controls[index],
- group: this.group,
+ this.onCustomEvent({
+ previousIndex: prevIndex,
+ index,
+ arrayModel: this.model,
model: this.model.groups[index].group[0],
- type: DynamicFormControlEventType.Change
- };
-
- this.onChange($event);
- }
+ control: (this.control as any).controls[index]
+ }, 'move');
+ }
}
update(event: any, index: number) {
@@ -68,4 +71,11 @@ export class DsDynamicFormArrayComponent extends DynamicFormArrayComponent {
this.onChange($event);
}
+
+ /**
+ * If the drag feature is disabled for this DynamicRowArrayModel.
+ */
+ get dragDisabled(): boolean {
+ return this.model.groups.length === 1 || !this.model.isDraggable;
+ }
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
index 8925d8fd87..d0b07de885 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-row-array-model.ts
@@ -9,6 +9,7 @@ export interface DynamicRowArrayModelConfig extends DynamicFormArrayModelConfig
metadataKey: string;
metadataFields: string[];
hasSelectableMetadata: boolean;
+ isDraggable: boolean;
}
export class DynamicRowArrayModel extends DynamicFormArrayModel {
@@ -19,6 +20,7 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
@serializable() metadataKey: string;
@serializable() metadataFields: string[];
@serializable() hasSelectableMetadata: boolean;
+ @serializable() isDraggable: boolean;
isRowArray = true;
constructor(config: DynamicRowArrayModelConfig, layout?: DynamicFormControlLayout) {
@@ -30,5 +32,6 @@ export class DynamicRowArrayModel extends DynamicFormArrayModel {
this.metadataKey = config.metadataKey;
this.metadataFields = config.metadataFields;
this.hasSelectableMetadata = config.hasSelectableMetadata;
+ this.isDraggable = config.isDraggable;
}
}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html
index 3442ddb1ba..339f6f278d 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.html
@@ -6,7 +6,8 @@
-
+
+
{
let component: DsDynamicLookupRelationModalComponent;
@@ -28,6 +32,7 @@ describe('DsDynamicLookupRelationModalComponent', () => {
let item;
let item1;
let item2;
+ let testWSI;
let searchResult1;
let searchResult2;
let listID;
@@ -41,6 +46,8 @@ describe('DsDynamicLookupRelationModalComponent', () => {
let lookupRelationService;
let rdbService;
let submissionId;
+ let submissionService;
+ let submissionObjectDataService;
const externalSources = [
Object.assign(new ExternalSource(), {
@@ -56,11 +63,16 @@ describe('DsDynamicLookupRelationModalComponent', () => {
];
const totalLocal = 10;
const totalExternal = 8;
+ const collection: Collection = new Collection();
+
function init() {
item = Object.assign(new Item(), { uuid: '7680ca97-e2bd-4398-bfa7-139a8673dc42', metadata: {} });
item1 = Object.assign(new Item(), { uuid: 'e1c51c69-896d-42dc-8221-1d5f2ad5516e' });
item2 = Object.assign(new Item(), { uuid: 'c8279647-1acc-41ae-b036-951d5f65649b' });
+ testWSI = new WorkspaceItem();
+ testWSI.item = createSuccessfulRemoteDataObject$(item);
+ testWSI.collection = createSuccessfulRemoteDataObject$(collection);
searchResult1 = Object.assign(new ItemSearchResult(), { indexableObject: item1 });
searchResult2 = Object.assign(new ItemSearchResult(), { indexableObject: item2 });
listID = '6b0c8221-fcb4-47a8-b483-ca32363fffb3';
@@ -87,6 +99,12 @@ describe('DsDynamicLookupRelationModalComponent', () => {
rdbService = jasmine.createSpyObj('rdbService', {
aggregate: createSuccessfulRemoteDataObject$(externalSources)
});
+ submissionService = jasmine.createSpyObj('SubmissionService', {
+ dispatchSave: jasmine.createSpy('dispatchSave')
+ });
+ submissionObjectDataService = jasmine.createSpyObj('SubmissionObjectDataService', {
+ findById: createSuccessfulRemoteDataObject$(testWSI)
+ });
submissionId = '1234';
}
@@ -111,6 +129,8 @@ describe('DsDynamicLookupRelationModalComponent', () => {
},
{ provide: RelationshipTypeService, useValue: {} },
{ provide: RemoteDataBuildService, useValue: rdbService },
+ { provide: SubmissionService, useValue: submissionService },
+ { provide: SubmissionObjectDataService, useValue: submissionObjectDataService },
{
provide: Store, useValue: {
// tslint:disable-next-line:no-empty
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
index 83b756157c..4ed972b2fa 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/dynamic-lookup-relation-modal.component.ts
@@ -11,7 +11,11 @@ import { ListableObject } from '../../../../object-collection/shared/listable-ob
import { RelationshipOptions } from '../../models/relationship-options.model';
import { SearchResult } from '../../../../search/search-result.model';
import { Item } from '../../../../../core/shared/item.model';
-import { getAllSucceededRemoteDataPayload } from '../../../../../core/shared/operators';
+import {
+ getAllSucceededRemoteData,
+ getAllSucceededRemoteDataPayload,
+ getRemoteDataPayload
+} from '../../../../../core/shared/operators';
import { AddRelationshipAction, RemoveRelationshipAction, UpdateRelationshipNameVariantAction } from './relationship.actions';
import { RelationshipService } from '../../../../../core/data/relationship.service';
import { RelationshipTypeService } from '../../../../../core/data/relationship-type.service';
@@ -23,6 +27,12 @@ import { ExternalSource } from '../../../../../core/shared/external-source.model
import { ExternalSourceService } from '../../../../../core/data/external-source.service';
import { Router } from '@angular/router';
import { RemoteDataBuildService } from '../../../../../core/cache/builders/remote-data-build.service';
+import { followLink } from '../../../../utils/follow-link-config.model';
+import { SubmissionObject } from '../../../../../core/submission/models/submission-object.model';
+import { Collection } from '../../../../../core/shared/collection.model';
+import { SubmissionService } from '../../../../../submission/submission.service';
+import { SubmissionObjectDataService } from '../../../../../core/submission/submission-object-data.service';
+import { RemoteData } from '../../../../../core/data/remote-data';
@Component({
selector: 'ds-dynamic-lookup-relation-modal',
@@ -112,6 +122,11 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
*/
totalExternal$: Observable;
+ /**
+ * List of subscriptions to unsubscribe from
+ */
+ private subs: Subscription[] = [];
+
constructor(
public modal: NgbActiveModal,
private selectableListService: SelectableListService,
@@ -121,14 +136,17 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
private lookupRelationService: LookupRelationService,
private searchConfigService: SearchConfigurationService,
private rdbService: RemoteDataBuildService,
+ private submissionService: SubmissionService,
+ private submissionObjectService: SubmissionObjectDataService,
private zone: NgZone,
private store: Store,
- private router: Router,
+ private router: Router
) {
}
ngOnInit(): void {
+ this.setItem();
this.selection$ = this.selectableListService
.getSelectableList(this.listId)
.pipe(map((listState: SelectableListState) => hasValue(listState) && hasValue(listState.selection) ? listState.selection : []));
@@ -188,6 +206,24 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
});
}
+ /**
+ * Initialize this.item$ based on this.model.submissionId
+ */
+ private setItem() {
+ const submissionObject$ = this.submissionObjectService
+ .findById(this.submissionId, true, true, followLink('item'), followLink('collection')).pipe(
+ getAllSucceededRemoteData(),
+ getRemoteDataPayload()
+ );
+
+ const item$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.item as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+ const collection$ = submissionObject$.pipe(switchMap((submissionObject: SubmissionObject) => (submissionObject.collection as Observable>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload())));
+
+ this.subs.push(item$.subscribe((item) => this.item = item));
+ this.subs.push(collection$.subscribe((collection) => this.collection = collection));
+
+ }
+
/**
* Add a subscription updating relationships with name variants
* @param sri The search result to track name variants for
@@ -243,5 +279,8 @@ export class DsDynamicLookupRelationModalComponent implements OnInit, OnDestroy
ngOnDestroy() {
this.router.navigate([], {});
Object.values(this.subMap).forEach((subscription) => subscription.unsubscribe());
+ this.subs
+ .filter((sub) => hasValue(sub))
+ .forEach((sub) => sub.unsubscribe());
}
}
diff --git a/src/app/shared/form/builder/form-builder.service.spec.ts b/src/app/shared/form/builder/form-builder.service.spec.ts
index b81bb3285b..cea4d7df6e 100644
--- a/src/app/shared/form/builder/form-builder.service.spec.ts
+++ b/src/app/shared/form/builder/form-builder.service.spec.ts
@@ -295,6 +295,7 @@ describe('FormBuilderService test suite', () => {
notRepeatable: false,
relationshipConfig: undefined,
submissionId: '1234',
+ isDraggable: true,
groupFactory: () => {
return [
new DynamicInputModel({ id: 'testFormRowArrayGroupInput' })
diff --git a/src/app/shared/form/builder/form-builder.service.ts b/src/app/shared/form/builder/form-builder.service.ts
index a8f6fa374c..85d70f20dc 100644
--- a/src/app/shared/form/builder/form-builder.service.ts
+++ b/src/app/shared/form/builder/form-builder.service.ts
@@ -28,9 +28,10 @@ import { DynamicRelationGroupModel } from './ds-dynamic-form-ui/models/relation-
import { DynamicRowArrayModel } from './ds-dynamic-form-ui/models/ds-dynamic-row-array-model';
import { DsDynamicInputModel } from './ds-dynamic-form-ui/models/ds-dynamic-input.model';
import { FormFieldMetadataValueObject } from './models/form-field-metadata-value.model';
-import { isNgbDateStruct } from '../../date.util';
+import { dateToString, isNgbDateStruct } from '../../date.util';
import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from './ds-dynamic-form-ui/ds-dynamic-form-constants';
import { CONCAT_GROUP_SUFFIX, DynamicConcatModel } from './ds-dynamic-form-ui/models/ds-dynamic-concat.model';
+import { VIRTUAL_METADATA_PREFIX } from '../../../core/shared/metadata.models';
@Injectable()
export class FormBuilderService extends DynamicFormService {
@@ -121,8 +122,15 @@ export class FormBuilderService extends DynamicFormService {
const normalizeValue = (controlModel, controlValue, controlModelIndex) => {
const controlLanguage = (controlModel as DsDynamicInputModel).hasLanguages ? (controlModel as DsDynamicInputModel).language : null;
+
+ if (controlModel?.metadataValue?.authority?.includes(VIRTUAL_METADATA_PREFIX)) {
+ return controlModel.metadataValue;
+ }
+
if (isString(controlValue)) {
return new FormFieldMetadataValueObject(controlValue, controlLanguage, null, null, controlModelIndex);
+ } else if (isNgbDateStruct(controlValue)) {
+ return new FormFieldMetadataValueObject(dateToString(controlValue));
} else if (isObject(controlValue)) {
const authority = (controlValue as any).authority || (controlValue as any).id || null;
const place = controlModelIndex || (controlValue as any).place;
@@ -240,7 +248,7 @@ export class FormBuilderService extends DynamicFormService {
}
hasArrayGroupValue(model: DynamicFormControlModel): boolean {
- return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG || model.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY);
+ return model && (this.isListGroup(model) || model.type === DYNAMIC_FORM_CONTROL_TYPE_TAG);
}
hasMappedGroupValue(model: DynamicFormControlModel): boolean {
@@ -310,7 +318,7 @@ export class FormBuilderService extends DynamicFormService {
let tempModel: DynamicFormControlModel;
if (this.isArrayGroup(model as DynamicFormControlModel)) {
- return hasValue((model as any).metadataKey) ? (model as any).metadataKey : model.index.toString();
+ return model.index.toString();
} else if (this.isModelInCustomGroup(model as DynamicFormControlModel)) {
tempModel = (model as any).parent;
} else {
diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts
index 09a3f53c58..8085924422 100644
--- a/src/app/shared/form/builder/parsers/concat-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts
@@ -58,8 +58,20 @@ export class ConcatFieldParser extends FieldParser {
concatGroup.group = [];
concatGroup.separator = this.separator;
- const input1ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_FIRST_INPUT_SUFFIX, false, false);
- const input2ModelConfig: DynamicInputModelConfig = this.initModel(id + CONCAT_SECOND_INPUT_SUFFIX, false, false);
+ const input1ModelConfig: DynamicInputModelConfig = this.initModel(
+ id + CONCAT_FIRST_INPUT_SUFFIX,
+ false,
+ true,
+ true,
+ false
+ );
+ const input2ModelConfig: DynamicInputModelConfig = this.initModel(
+ id + CONCAT_SECOND_INPUT_SUFFIX,
+ false,
+ true,
+ true,
+ false
+ );
if (hasNoValue(concatGroup.hint) && hasValue(input1ModelConfig.hint) && hasNoValue(input2ModelConfig.hint)) {
concatGroup.hint = input1ModelConfig.hint;
diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts
index b623a98078..da304ca267 100644
--- a/src/app/shared/form/builder/parsers/field-parser.ts
+++ b/src/app/shared/form/builder/parsers/field-parser.ts
@@ -15,6 +15,8 @@ import { setLayout } from './parser.utils';
import { ParserOptions } from './parser-options';
import { RelationshipOptions } from '../models/relationship-options.model';
import { VocabularyOptions } from '../../../../core/submission/vocabularies/models/vocabulary-options.model';
+import { ParserType } from './parser-type';
+import { isNgbDateStruct } from '../../../date.util';
export const SUBMISSION_ID: InjectionToken = new InjectionToken('submissionId');
export const CONFIG_DATA: InjectionToken = new InjectionToken('configData');
@@ -37,9 +39,8 @@ export abstract class FieldParser {
public parse() {
if (((this.getInitValueCount() > 1 && !this.configData.repeatable) || (this.configData.repeatable))
- && (this.configData.input.type !== 'list')
- && (this.configData.input.type !== 'tag')
- && (this.configData.input.type !== 'group')
+ && (this.configData.input.type !== ParserType.List)
+ && (this.configData.input.type !== ParserType.Tag)
) {
let arrayCounter = 0;
let fieldArrayCounter = 0;
@@ -49,6 +50,11 @@ export abstract class FieldParser {
if (Array.isArray(this.configData.selectableMetadata) && this.configData.selectableMetadata.length === 1) {
metadataKey = this.configData.selectableMetadata[0].metadata;
}
+
+ let isDraggable = true;
+ if (this.configData.input.type === ParserType.Onebox && this.configData?.selectableMetadata?.length > 1) {
+ isDraggable = false;
+ }
const config = {
id: uniqueId() + '_array',
label: this.configData.label,
@@ -60,6 +66,7 @@ export abstract class FieldParser {
metadataKey,
metadataFields: this.getAllFieldIds(),
hasSelectableMetadata: isNotEmpty(this.configData.selectableMetadata),
+ isDraggable,
groupFactory: () => {
let model;
if ((arrayCounter === 0)) {
@@ -69,19 +76,13 @@ export abstract class FieldParser {
const fieldArrayOfValueLength = this.getInitValueCount(arrayCounter - 1);
let fieldValue = null;
if (fieldArrayOfValueLength > 0) {
- if (fieldArrayCounter === 0) {
- fieldValue = '';
- } else {
- fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter - 1);
- }
- fieldArrayCounter++;
- if (fieldArrayCounter === fieldArrayOfValueLength + 1) {
+ fieldValue = this.getInitFieldValue(arrayCounter - 1, fieldArrayCounter++);
+ if (fieldArrayCounter === fieldArrayOfValueLength) {
fieldArrayCounter = 0;
arrayCounter++;
}
}
model = this.modelFactory(fieldValue, false);
- model.id = `${model.id}_${fieldArrayCounter}`;
}
setLayout(model, 'element', 'host', 'col');
if (model.hasLanguages || isNotEmpty(model.relationship)) {
@@ -130,7 +131,9 @@ export abstract class FieldParser {
return;
}
- if (typeof fieldValue === 'object') {
+ if (isNgbDateStruct(fieldValue)) {
+ modelConfig.value = fieldValue;
+ } else if (typeof fieldValue === 'object') {
modelConfig.metadataValue = fieldValue;
modelConfig.language = fieldValue.language;
modelConfig.place = fieldValue.place;
@@ -210,10 +213,9 @@ export abstract class FieldParser {
}
protected getInitArrayIndex() {
- let fieldCount = 0;
const fieldIds: any = this.getAllFieldIds();
if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length === 1 && this.initFormValues.hasOwnProperty(fieldIds)) {
- fieldCount = this.initFormValues[fieldIds].filter((value) => hasValue(value) && hasValue(value.value)).length;
+ return this.initFormValues[fieldIds].length;
} else if (isNotEmpty(this.initFormValues) && isNotNull(fieldIds) && fieldIds.length > 1) {
let counter = 0;
fieldIds.forEach((id) => {
@@ -221,9 +223,10 @@ export abstract class FieldParser {
counter = counter + this.initFormValues[id].length;
}
});
- fieldCount = counter;
+ return (counter === 0) ? 1 : counter;
+ } else {
+ return 1;
}
- return (fieldCount === 0) ? 1 : fieldCount + 1;
}
protected getFieldId(): string {
@@ -245,7 +248,7 @@ export abstract class FieldParser {
}
}
- protected initModel(id?: string, label = true, setErrors = true, hint = true) {
+ protected initModel(id?: string, label = true, labelEmpty = false, setErrors = true, hint = true) {
const controlModel = Object.create(null);
@@ -316,7 +319,7 @@ export abstract class FieldParser {
protected setLabel(controlModel, label = true, labelEmpty = false) {
if (label) {
- controlModel.label = this.configData.label;
+ controlModel.label = (labelEmpty) ? ' ' : this.configData.label;
}
}
diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.ts
index b9ac531c68..3dafa4eebd 100644
--- a/src/app/shared/form/builder/parsers/onebox-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/onebox-field-parser.ts
@@ -59,7 +59,7 @@ export class OneboxFieldParser extends FieldParser {
this.setLabel(inputSelectGroup, label);
inputSelectGroup.required = isNotEmpty(this.configData.mandatory);
- const selectModelConfig: DynamicSelectModelConfig = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label, false);
+ const selectModelConfig: DynamicSelectModelConfig = this.initModel(newId + QUALDROP_METADATA_SUFFIX, label, false, false);
selectModelConfig.hint = null;
this.setOptions(selectModelConfig);
if (isNotEmpty(fieldValue)) {
@@ -67,7 +67,7 @@ export class OneboxFieldParser extends FieldParser {
}
inputSelectGroup.group.push(new DynamicSelectModel(selectModelConfig, clsSelect));
- const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false);
+ const inputModelConfig: DsDynamicInputModelConfig = this.initModel(newId + QUALDROP_VALUE_SUFFIX, label, false, false);
inputModelConfig.hint = null;
this.setValues(inputModelConfig, fieldValue);
inputSelectGroup.readOnly = selectModelConfig.disabled && inputModelConfig.readOnly;
diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html
index 97879cc025..10ec8da3b9 100644
--- a/src/app/shared/form/form.component.html
+++ b/src/app/shared/form/form.component.html
@@ -1,57 +1,68 @@