diff --git a/package.json b/package.json
index 91117723d1..bf9741c870 100644
--- a/package.json
+++ b/package.json
@@ -130,6 +130,7 @@
"devDependencies": {
"@angular/compiler": "^6.1.4",
"@angular/compiler-cli": "^6.1.4",
+ "@fortawesome/fontawesome-free": "^5.5.0",
"@ngrx/entity": "^6.1.0",
"@ngrx/schematics": "^6.1.0",
"@ngrx/store-devtools": "^6.1.0",
@@ -182,7 +183,7 @@
"karma-webdriver-launcher": "1.0.5",
"karma-webpack": "3.0.0",
"ngrx-store-freeze": "^0.2.4",
- "node-sass": "^4.7.2",
+ "node-sass": "^4.11.0",
"nodemon": "^1.15.0",
"npm-run-all": "4.1.3",
"postcss": "^7.0.2",
diff --git a/resources/i18n/en.json b/resources/i18n/en.json
index d3ddf1e985..6eb0d19c4b 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json
@@ -49,9 +49,19 @@
}
},
"nav": {
- "home": "Home",
+ "browse": {
+ "header": "All of DSpace"
+ },
+ "community-browse": {
+ "header": "By Community"
+ },
+ "statistics": {
+ "header": "Statistics"
+ },
"login": "Log In",
- "logout": "Log Out"
+ "logout": "Log Out",
+ "language": "Language switch",
+ "search": "Search"
},
"pagination": {
"results-per-page": "Results Per Page",
@@ -207,6 +217,85 @@
}
}
},
+ "menu": {
+ "header": {
+ "admin": "Admin",
+ "image": {
+ "logo": "Repository logo"
+ }
+ },
+ "section": {
+ "pin": "Pin sidebar",
+ "unpin": "Unpin sidebar",
+ "new": "New",
+ "new_community": "Community",
+ "new_collection": "Collection",
+ "new_item": "Item",
+ "new_item_version": "Item Version",
+ "edit": "Edit",
+ "edit_community": "Community",
+ "edit_collection": "Collection",
+ "edit_item": "Item",
+ "import": "Import",
+ "import_metadata": "Metadata",
+ "import_batch": "Batch Import (ZIP)",
+ "export": "Export",
+ "export_community": "Community",
+ "export_collection": "Collection",
+ "export_item": "Item",
+ "export_metadata": "Metadata",
+ "access_control": "Access Control",
+ "access_control_people": "People",
+ "access_control_groups": "Groups",
+ "access_control_authorizations": "Authorizations",
+ "find": "Find",
+ "find_items": "Items",
+ "find_withdrawn_items": "Withdrawn Items",
+ "find_private_items": "Private Items",
+ "registries": "Registries",
+ "registries_metadata": "Metadata",
+ "registries_format": "Format",
+ "curation_task": "Curation Task",
+ "statistics_task": "Statistics Task",
+ "control_panel": "Control Panel",
+ "browse_global": "All of DSpace",
+ "browse_global_communities_and_collections": "Communities & Collections",
+ "browse_global_by_issue_date": "By Issue Date",
+ "browse_global_by_author": "By Author",
+ "browse_global_by_title": "By Title",
+ "statistics": "Statistics",
+ "browse_community": "This Community",
+ "browse_community_by_issue_date": "By Issue Date",
+ "browse_community_by_author": "By Author",
+ "browse_community_by_title": "By Title",
+ "icon": {
+ "pin": "Pin sidebar",
+ "unpin": "Unpin sidebar",
+ "new": "New menu section",
+ "edit": "Edit menu section",
+ "import": "Import menu section",
+ "export": "Export menu section",
+ "access_control": "Access Control menu section",
+ "find": "Find menu section",
+ "registries": "Registries menu section",
+ "curation_task": "Curation Task menu section",
+ "statistics_task": "Statistics Task menu section",
+ "control_panel": "Control Panel menu section"
+ },
+ "toggle": {
+ "new": "Toggle New section",
+ "edit": "Toggle Edit section",
+ "import": "Toggle Import section",
+ "export": "Toggle Export section",
+ "access_control": "Toggle Access Control section",
+ "find": "Toggle Find section",
+ "registries": "Toggle Registries section",
+ "curation_task": "Toggle Curation Task section",
+ "statistics_task": "Toggle Statistics Task section",
+ "control_panel": "Toggle Control Panel section"
+ }
+ }
+ },
"loading": {
"default": "Loading...",
"top-level-communities": "Loading top-level communities...",
@@ -279,5 +368,8 @@
"errors": {
"invalid-user": "Invalid email address or password."
}
+ },
+ "chips": {
+ "remove": "Remove chip"
}
}
diff --git a/resources/images/dspace-logo-mini.svg b/resources/images/dspace-logo-mini.svg
new file mode 100644
index 0000000000..6ca41addc8
--- /dev/null
+++ b/resources/images/dspace-logo-mini.svg
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/resources/images/dspace-logo.svg b/resources/images/dspace-logo.svg
new file mode 100644
index 0000000000..60df1ed46d
--- /dev/null
+++ b/resources/images/dspace-logo.svg
@@ -0,0 +1,37 @@
+
+
+
+
diff --git a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts
index b2cc5129ce..6c5e01b37d 100644
--- a/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts
+++ b/src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts
@@ -7,7 +7,6 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { MetadataField } from '../../../core/metadata/metadatafield.model';
import { MetadataSchema } from '../../../core/metadata/metadataschema.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
-import { SortOptions } from '../../../core/cache/models/sort-options.model';
@Component({
selector: 'ds-metadata-schema',
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html
new file mode 100644
index 0000000000..e72a17aac1
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.scss b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.scss
new file mode 100644
index 0000000000..88eb98509a
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.scss
@@ -0,0 +1 @@
+@import '../../../../styles/variables.scss';
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts
new file mode 100644
index 0000000000..30c57c17ea
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.spec.ts
@@ -0,0 +1,60 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MenuService } from '../../../shared/menu/menu.service';
+import { MenuServiceStub } from '../../../shared/testing/menu-service-stub';
+import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
+import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service-stub';
+import { Component } from '@angular/core';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { AdminSidebarSectionComponent } from './admin-sidebar-section.component';
+import { RouterTestingModule } from '@angular/router/testing';
+import { By } from '@angular/platform-browser';
+import { TranslateModule } from '@ngx-translate/core';
+
+describe('AdminSidebarSectionComponent', () => {
+ let component: AdminSidebarSectionComponent;
+ let fixture: ComponentFixture;
+ const menuService = new MenuServiceStub();
+ const iconString = 'test';
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule, RouterTestingModule, TranslateModule.forRoot()],
+ declarations: [AdminSidebarSectionComponent, TestComponent],
+ providers: [
+ { provide: 'sectionDataProvider', useValue: { model: { link: 'google.com' }, icon: iconString } },
+ { provide: MenuService, useValue: menuService },
+ { provide: CSSVariableService, useClass: CSSVariableServiceStub },
+ ]
+ }).overrideComponent(AdminSidebarSectionComponent, {
+ set: {
+ entryComponents: [TestComponent]
+ }
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(AdminSidebarSectionComponent);
+ component = fixture.componentInstance;
+ spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should set the right icon', () => {
+ const icon = fixture.debugElement.query(By.css('.shortcut-icon')).query(By.css('i.fas'));
+ expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
+ });
+});
+
+// declare a test component
+@Component({
+ selector: 'ds-test-cmp',
+ template: ``
+})
+class TestComponent {
+}
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts
new file mode 100644
index 0000000000..a19a1f95e4
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component.ts
@@ -0,0 +1,34 @@
+import { Component, Inject, Injector, OnInit } from '@angular/core';
+import { MenuSectionComponent } from '../../../shared/menu/menu-section/menu-section.component';
+import { MenuID } from '../../../shared/menu/initial-menus-state';
+import { MenuService } from '../../../shared/menu/menu.service';
+import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
+import { LinkMenuItemModel } from '../../../shared/menu/menu-item/models/link.model';
+import { MenuSection } from '../../../shared/menu/menu.reducer';
+
+/**
+ * Represents a non-expandable section in the admin sidebar
+ */
+@Component({
+ selector: 'ds-admin-sidebar-section',
+ templateUrl: './admin-sidebar-section.component.html',
+ styleUrls: ['./admin-sidebar-section.component.scss'],
+
+})
+@rendersSectionForMenu(MenuID.ADMIN, false)
+export class AdminSidebarSectionComponent extends MenuSectionComponent implements OnInit {
+
+ /**
+ * This section resides in the Admin Sidebar
+ */
+ menuID: MenuID = MenuID.ADMIN;
+ itemModel;
+ constructor(@Inject('sectionDataProvider') menuSection: MenuSection, protected menuService: MenuService, protected injector: Injector,) {
+ super(menuSection, menuService, injector);
+ this.itemModel = menuSection.model as LinkMenuItemModel;
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ }
+}
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.html b/src/app/+admin/admin-sidebar/admin-sidebar.component.html
new file mode 100644
index 0000000000..fc9e707bcd
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.html
@@ -0,0 +1,52 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.scss b/src/app/+admin/admin-sidebar/admin-sidebar.component.scss
new file mode 100644
index 0000000000..9236cd2a0d
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.scss
@@ -0,0 +1,77 @@
+@import '../../../styles/variables.scss';
+@import '../../../styles/mixins.scss';
+$icon-z-index: 10;
+
+:host {
+ left: 0;
+ top: 0;
+ height: 100vh;
+ flex: 1 1 auto;
+ nav {
+ height: 100%;
+ flex-direction: column;
+ > div {
+ width: 100%;
+ &.sidebar-top-level-items {
+ flex: 1;
+ overflow: auto;
+ @include dark-scrollbar;
+ }
+ }
+
+ &.inactive ::ng-deep .sidebar-collapsible {
+ margin-left: -#{$sidebar-items-width};
+ }
+
+ .navbar-nav {
+ .admin-menu-header {
+ background-color: $admin-sidebar-header-bg;
+ .logo-wrapper {
+ img {
+ height: 20px;
+ }
+ }
+ .section-header-text {
+ line-height: 1.5;
+ }
+
+ }
+ }
+
+
+ ::ng-deep {
+ .navbar-nav {
+ .sidebar-section {
+ display: flex;
+ align-content: stretch;
+ background-color: $dark;
+ .nav-item {
+ padding-top: $spacer;
+ padding-bottom: $spacer;
+ }
+ .shortcut-icon {
+ padding-left: $icon-padding;
+ padding-right: $icon-padding;
+ }
+ .shortcut-icon, .icon-wrapper {
+ background-color: inherit;
+ z-index: $icon-z-index;
+ }
+ .sidebar-collapsible {
+ width: $sidebar-items-width;
+ position: relative;
+ a {
+ padding-right: $spacer;
+ width: 100%;
+ }
+ }
+ &.active > .sidebar-collapsible > .nav-link {
+ color: $navbar-dark-active-color;
+ }
+ }
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
new file mode 100644
index 0000000000..c99e8adc58
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts
@@ -0,0 +1,142 @@
+import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TranslateModule } from '@ngx-translate/core';
+import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
+import { AdminSidebarComponent } from './admin-sidebar.component';
+import { MenuService } from '../../shared/menu/menu.service';
+import { MenuServiceStub } from '../../shared/testing/menu-service-stub';
+import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
+import { CSSVariableServiceStub } from '../../shared/testing/css-variable-service-stub';
+import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
+import { AuthService } from '../../core/auth/auth.service';
+
+import { of as observableOf } from 'rxjs';
+import { By } from '@angular/platform-browser';
+
+describe('AdminSidebarComponent', () => {
+ let comp: AdminSidebarComponent;
+ let fixture: ComponentFixture;
+ const menuService = new MenuServiceStub();
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), NoopAnimationsModule],
+ declarations: [AdminSidebarComponent],
+ providers: [
+ { provide: Injector, useValue: {} },
+ { provide: MenuService, useValue: menuService },
+ { provide: CSSVariableService, useClass: CSSVariableServiceStub },
+ { provide: AuthService, useClass: AuthServiceStub }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(AdminSidebarComponent, {
+ set: {
+ changeDetection: ChangeDetectionStrategy.Default,
+ }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ spyOn(menuService, 'getMenuTopSections').and.returnValue(observableOf([]));
+ fixture = TestBed.createComponent(AdminSidebarComponent);
+ comp = fixture.componentInstance; // SearchPageComponent test instance
+ comp.sections = observableOf([]);
+ fixture.detectChanges();
+ });
+
+ describe('startSlide', () => {
+ describe('when expanding', () => {
+ beforeEach(() => {
+ comp.sidebarClosed = true;
+ comp.startSlide({ toState: 'expanded' } as any);
+ });
+
+ it('should set the sidebarClosed to false', () => {
+ expect(comp.sidebarClosed).toBeFalsy();
+ })
+ });
+
+ describe('when collapsing', () => {
+ beforeEach(() => {
+ comp.sidebarClosed = false;
+ comp.startSlide({ toState: 'collapsed' } as any);
+ });
+
+ it('should set the sidebarOpen to false', () => {
+ expect(comp.sidebarOpen).toBeFalsy();
+ })
+ })
+ });
+
+ describe('finishSlide', () => {
+ describe('when expanding', () => {
+ beforeEach(() => {
+ comp.sidebarClosed = true;
+ comp.startSlide({ fromState: 'expanded' } as any);
+ });
+
+ it('should set the sidebarClosed to true', () => {
+ expect(comp.sidebarClosed).toBeTruthy();
+ })
+ });
+
+ describe('when collapsing', () => {
+ beforeEach(() => {
+ comp.sidebarClosed = false;
+ comp.startSlide({ fromState: 'collapsed' } as any);
+ });
+
+ it('should set the sidebarOpen to true', () => {
+ expect(comp.sidebarOpen).toBeTruthy();
+ })
+ })
+ });
+
+ describe('when the collapse icon is clicked', () => {
+ beforeEach(() => {
+ spyOn(menuService, 'toggleMenu');
+ const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('a.shortcut-icon'));
+ sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
+ });
+
+ it('should call toggleMenu on the menuService', () => {
+ expect(menuService.toggleMenu).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the collapse link is clicked', () => {
+ beforeEach(() => {
+ spyOn(menuService, 'toggleMenu');
+ const sidebarToggler = fixture.debugElement.query(By.css('#sidebar-collapse-toggle')).query(By.css('.sidebar-collapsible')).query(By.css('a'));
+ sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
+ });
+
+ it('should call toggleMenu on the menuService', () => {
+ expect(menuService.toggleMenu).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the the mouse enters the nav tag', () => {
+ it('should call expandPreview on the menuService after 100ms', fakeAsync(() => {
+ spyOn(menuService, 'expandMenuPreview');
+ const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
+ sidebarToggler.triggerEventHandler('mouseenter', {preventDefault: () => {/**/}});
+ tick(99);
+ expect(menuService.expandMenuPreview).not.toHaveBeenCalled();
+ tick(1);
+ expect(menuService.expandMenuPreview).toHaveBeenCalled();
+ }));
+ });
+
+ describe('when the the mouse leaves the nav tag', () => {
+ it('should call collapseMenuPreview on the menuService after 400ms', fakeAsync(() => {
+ spyOn(menuService, 'collapseMenuPreview');
+ const sidebarToggler = fixture.debugElement.query(By.css('nav.navbar'));
+ sidebarToggler.triggerEventHandler('mouseleave', {preventDefault: () => {/**/}});
+ tick(399);
+ expect(menuService.collapseMenuPreview).not.toHaveBeenCalled();
+ tick(1);
+ expect(menuService.collapseMenuPreview).toHaveBeenCalled();
+ }));
+ });
+});
diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
new file mode 100644
index 0000000000..eb48f64d4d
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts
@@ -0,0 +1,479 @@
+import { Component, Injector, OnInit } from '@angular/core';
+import { Observable } from 'rxjs/internal/Observable';
+import { slide, slideHorizontal, slideSidebar } from '../../shared/animations/slide';
+import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
+import { MenuService } from '../../shared/menu/menu.service';
+import { MenuID, MenuItemType } from '../../shared/menu/initial-menus-state';
+import { MenuComponent } from '../../shared/menu/menu.component';
+import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model';
+import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model';
+import { AuthService } from '../../core/auth/auth.service';
+import { first, map } from 'rxjs/operators';
+import { combineLatest as combineLatestObservable } from 'rxjs';
+
+/**
+ * Component representing the admin sidebar
+ */
+@Component({
+ selector: 'ds-admin-sidebar',
+ templateUrl: './admin-sidebar.component.html',
+ styleUrls: ['./admin-sidebar.component.scss'],
+ animations: [slideHorizontal, slideSidebar]
+})
+export class AdminSidebarComponent extends MenuComponent implements OnInit {
+ /**
+ * The menu ID of the Navbar is PUBLIC
+ * @type {MenuID.ADMIN}
+ */
+ menuID = MenuID.ADMIN;
+
+ /**
+ * Observable that emits the width of the collapsible menu sections
+ */
+ sidebarWidth: Observable;
+
+ /**
+ * Is true when the sidebar is open, is false when the sidebar is animating or closed
+ * @type {boolean}
+ */
+ sidebarOpen = true; // Open in UI, animation finished
+
+ /**
+ * Is true when the sidebar is closed, is false when the sidebar is animating or open
+ * @type {boolean}
+ */
+ sidebarClosed = !this.sidebarOpen; // Closed in UI, animation finished
+
+ /**
+ * Emits true when either the menu OR the menu's preview is expanded, else emits false
+ */
+ sidebarExpanded: Observable;
+
+ constructor(protected menuService: MenuService,
+ protected injector: Injector,
+ private variableService: CSSVariableService,
+ private authService: AuthService
+ ) {
+ super(menuService, injector);
+ }
+
+ /**
+ * Set and calculate all initial values of the instance variables
+ */
+ ngOnInit(): void {
+ this.createMenu();
+ super.ngOnInit();
+ this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
+ this.authService.isAuthenticated()
+ .subscribe((loggedIn: boolean) => {
+ if (loggedIn) {
+ this.menuService.showMenu(this.menuID);
+ }
+ });
+ this.menuCollapsed.pipe(first())
+ .subscribe((collapsed: boolean) => {
+ this.sidebarOpen = !collapsed;
+ this.sidebarClosed = collapsed;
+ });
+ this.sidebarExpanded = combineLatestObservable(this.menuCollapsed, this.menuPreviewCollapsed)
+ .pipe(
+ map(([collapsed, previewCollapsed]) => (!collapsed || !previewCollapsed))
+ );
+ }
+
+ /**
+ * Initialize all menu sections and items for this menu
+ */
+ private 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.LINK,
+ text: 'menu.section.new_community',
+ link: '/communities/submission'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'new_collection',
+ parentID: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.new_collection',
+ link: '/collections/submission'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'new_item',
+ parentID: 'new',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.new_item',
+ link: '/items/submission'
+ } 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,
+ },
+
+ /* 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.LINK,
+ text: 'menu.section.edit_community',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'edit_collection',
+ parentID: 'edit',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.edit_collection',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'edit_item',
+ parentID: 'edit',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.edit_item',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+
+ /* Import */
+ {
+ id: 'import',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.import'
+ } as TextMenuItemModel,
+ icon: 'sign-in-alt',
+ index: 2
+ },
+ {
+ id: 'import_metadata',
+ parentID: 'import',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.import_metadata',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'import_batch',
+ parentID: 'import',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.import_batch',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+
+ /* Export */
+ {
+ id: 'export',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.export'
+ } as TextMenuItemModel,
+ icon: 'sign-out-alt',
+ index: 3
+ },
+ {
+ id: 'export_community',
+ parentID: 'export',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.export_community',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'export_collection',
+ parentID: 'export',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.export_collection',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'export_item',
+ parentID: 'export',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.export_item',
+ link: '#'
+ } as LinkMenuItemModel,
+ }, {
+ id: 'export_metadata',
+ parentID: 'export',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.export_metadata',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+
+ /* Access Control */
+ {
+ id: 'access_control',
+ active: false,
+ visible: true,
+ 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: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_people',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'access_control_groups',
+ parentID: 'access_control',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_groups',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'access_control_authorizations',
+ parentID: 'access_control',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.access_control_authorizations',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+
+ /* Search */
+ {
+ id: 'find',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.find'
+ } as TextMenuItemModel,
+ icon: 'search',
+ index: 5
+ },
+ {
+ id: 'find_items',
+ parentID: 'find',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.find_items',
+ link: '/search'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'find_withdrawn_items',
+ parentID: 'find',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.find_withdrawn_items',
+ link: '#'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'find_private_items',
+ parentID: 'find',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.find_private_items',
+ link: '/admin/items'
+ } as LinkMenuItemModel,
+ },
+
+ /* Registries */
+ {
+ id: 'registries',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.TEXT,
+ text: 'menu.section.registries'
+ } as TextMenuItemModel,
+ icon: 'list',
+ index: 6
+ },
+ {
+ id: 'registries_metadata',
+ parentID: 'registries',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.registries_metadata',
+ link: 'admin/registries/metadata'
+ } as LinkMenuItemModel,
+ },
+ {
+ id: 'registries_format',
+ parentID: 'registries',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.registries_format',
+ link: 'admin/registries/bitstream-formats'
+ } as LinkMenuItemModel,
+ },
+
+ /* Curation tasks */
+ {
+ id: 'curation_tasks',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: 'menu.section.curation_task',
+ link: '/curation'
+ } as LinkMenuItemModel,
+ icon: 'filter',
+ index: 7
+ },
+
+ /* 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
+ },
+
+ /* 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
+ },
+ ];
+ menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, menuSection));
+
+ }
+
+ /**
+ * Method to change this.collapsed to false when the slide animation ends and is sliding open
+ * @param event The animation event
+ */
+ startSlide(event: any): void {
+ if (event.toState === 'expanded') {
+ this.sidebarClosed = false;
+ } else if (event.toState === 'collapsed') {
+ this.sidebarOpen = false;
+ }
+ }
+
+ /**
+ * Method to change this.collapsed to false when the slide animation ends and is sliding open
+ * @param event The animation event
+ */
+ finishSlide(event: any): void {
+ if (event.fromState === 'expanded') {
+ this.sidebarClosed = true;
+ } else if (event.fromState === 'collapsed') {
+ this.sidebarOpen = true;
+ }
+ }
+}
diff --git a/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html
new file mode 100644
index 0000000000..808683910e
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.html
@@ -0,0 +1,27 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss
new file mode 100644
index 0000000000..779cba09d9
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.scss
@@ -0,0 +1,21 @@
+@import '../../../../styles/variables.scss';
+
+::ng-deep {
+ .fa-chevron-right {
+ padding-left: $spacer/2;
+ font-size: 0.5rem;
+ line-height: 3;
+ }
+
+ .sidebar-sub-level-items {
+ list-style: disc;
+ color: $navbar-dark-color;
+ overflow: hidden;
+
+ }
+
+ .sidebar-collapsible {
+ display: flex;
+ flex-direction: column;
+ }
+}
\ No newline at end of file
diff --git a/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts
new file mode 100644
index 0000000000..787386932a
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.spec.ts
@@ -0,0 +1,84 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ExpandableAdminSidebarSectionComponent } from './expandable-admin-sidebar-section.component';
+import { MenuService } from '../../../shared/menu/menu.service';
+import { MenuServiceStub } from '../../../shared/testing/menu-service-stub';
+import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
+import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service-stub';
+import { of as observableOf } from 'rxjs';
+import { Component } from '@angular/core';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { By } from '@angular/platform-browser';
+import { TranslateModule } from '@ngx-translate/core';
+
+describe('ExpandableAdminSidebarSectionComponent', () => {
+ let component: ExpandableAdminSidebarSectionComponent;
+ let fixture: ComponentFixture;
+ const menuService = new MenuServiceStub();
+ const iconString = 'test';
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule, TranslateModule.forRoot()],
+ declarations: [ExpandableAdminSidebarSectionComponent, TestComponent],
+ providers: [
+ { provide: 'sectionDataProvider', useValue: {icon: iconString} },
+ { provide: MenuService, useValue: menuService },
+ { provide: CSSVariableService, useClass: CSSVariableServiceStub },
+ ]
+ }).overrideComponent(ExpandableAdminSidebarSectionComponent, {
+ set: {
+ entryComponents: [TestComponent]
+ }
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ spyOn(menuService, 'getSubSectionsByParentID').and.returnValue(observableOf([]));
+ fixture = TestBed.createComponent(ExpandableAdminSidebarSectionComponent);
+ component = fixture.componentInstance;
+ spyOn(component as any, 'getMenuItemComponent').and.returnValue(TestComponent);
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should set the right icon', () => {
+ const icon = fixture.debugElement.query(By.css('.icon-wrapper')).query(By.css('i.fas'));
+ expect(icon.nativeElement.getAttribute('class')).toContain('fa-' + iconString);
+ });
+
+ describe('when the icon is clicked', () => {
+ beforeEach(() => {
+ spyOn(menuService, 'toggleActiveSection');
+ const sidebarToggler = fixture.debugElement.query(By.css('a.shortcut-icon'));
+ sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
+ });
+
+ it('should call toggleActiveSection on the menuService', () => {
+ expect(menuService.toggleActiveSection).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the header text is clicked', () => {
+ beforeEach(() => {
+ spyOn(menuService, 'toggleActiveSection');
+ const sidebarToggler = fixture.debugElement.query(By.css('.sidebar-collapsible')).query(By.css('a'));
+ sidebarToggler.triggerEventHandler('click', {preventDefault: () => {/**/}});
+ });
+
+ it('should call toggleActiveSection on the menuService', () => {
+ expect(menuService.toggleActiveSection).toHaveBeenCalled();
+ });
+ });
+});
+
+// declare a test component
+@Component({
+ selector: 'ds-test-cmp',
+ template: ``
+})
+class TestComponent {
+}
diff --git a/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts
new file mode 100644
index 0000000000..4921be77e2
--- /dev/null
+++ b/src/app/+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component.ts
@@ -0,0 +1,69 @@
+import { Component, Inject, Injector, OnInit } from '@angular/core';
+import { rotate } from '../../../shared/animations/rotate';
+import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component';
+import { slide } from '../../../shared/animations/slide';
+import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
+import { bgColor } from '../../../shared/animations/bgColor';
+import { MenuID } from '../../../shared/menu/initial-menus-state';
+import { MenuService } from '../../../shared/menu/menu.service';
+import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorator';
+
+/**
+ * Represents a expandable section in the sidebar
+ */
+@Component({
+ selector: 'ds-expandable-admin-sidebar-section',
+ templateUrl: './expandable-admin-sidebar-section.component.html',
+ styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
+ animations: [rotate, slide, bgColor]
+
+})
+@rendersSectionForMenu(MenuID.ADMIN, true)
+export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
+ /**
+ * This section resides in the Admin Sidebar
+ */
+ menuID = MenuID.ADMIN;
+
+ /**
+ * The background color of the section when it's active
+ */
+ sidebarActiveBg;
+
+ /**
+ * Emits true when the sidebar is currently collapsed, true when it's expanded
+ */
+ sidebarCollapsed: Observable;
+
+ /**
+ * Emits true when the sidebar's preview is currently collapsed, true when it's expanded
+ */
+ sidebarPreviewCollapsed: Observable;
+
+ /**
+ * Emits true when the menu section is expanded, else emits false
+ * This is true when the section is active AND either the sidebar or it's preview is open
+ */
+ expanded: Observable;
+
+ constructor(@Inject('sectionDataProvider') menuSection, protected menuService: MenuService,
+ private variableService: CSSVariableService, protected injector: Injector) {
+ super(menuSection, menuService, injector);
+ }
+
+ /**
+ * Set initial values for instance variables
+ */
+ ngOnInit(): void {
+ super.ngOnInit();
+ this.sidebarActiveBg = this.variableService.getVariable('adminSidebarActiveBg');
+ this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
+ this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
+ this.expanded = combineLatestObservable(this.active, this.sidebarCollapsed, this.sidebarPreviewCollapsed)
+ .pipe(
+ map(([active, sidebarCollapsed, sidebarPreviewCollapsed]) => (active && (!sidebarCollapsed || !sidebarPreviewCollapsed)))
+ );
+ }
+}
diff --git a/src/app/+admin/admin.module.ts b/src/app/+admin/admin.module.ts
index b979813376..41d00223ab 100644
--- a/src/app/+admin/admin.module.ts
+++ b/src/app/+admin/admin.module.ts
@@ -5,7 +5,7 @@ import { AdminRoutingModule } from './admin-routing.module';
@NgModule({
imports: [
AdminRegistriesModule,
- AdminRoutingModule
+ AdminRoutingModule,
]
})
export class AdminModule {
diff --git a/src/app/+home-page/home-news/home-news.component.scss b/src/app/+home-page/home-news/home-news.component.scss
index 4f4c6df128..a1bb43af91 100644
--- a/src/app/+home-page/home-news/home-news.component.scss
+++ b/src/app/+home-page/home-news/home-news.component.scss
@@ -8,6 +8,9 @@
.dspace-logo-container {
margin: 10px 20px 0px 20px;
+ .display-3 {
+ word-break: break-word;
+ }
}
.dspace-logo-container img {
diff --git a/src/app/+logout-page/logout-page.component.html b/src/app/+logout-page/logout-page.component.html
index 9c6185b665..b5012ed53b 100644
--- a/src/app/+logout-page/logout-page.component.html
+++ b/src/app/+logout-page/logout-page.component.html
@@ -1,6 +1,6 @@
-
+
{{"logout.form.header" | translate}}
diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.html b/src/app/+search-page/search-filters/search-filter/search-filter.component.html
index f5dc5fff38..1013bf7e28 100644
--- a/src/app/+search-page/search-filters/search-filter/search-filter.component.html
+++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.html
@@ -1,5 +1,5 @@
-
{{'search.filters.filter.' + filter.name + '.head'| translate}}
{{'search.filters.filter.' + filter.name + '.head'| translate}}
diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts
index 7f1eb513ea..f16faff1f3 100644
--- a/src/app/+search-page/search-filters/search-filters.component.ts
+++ b/src/app/+search-page/search-filters/search-filters.component.ts
@@ -58,7 +58,6 @@ export class SearchFiltersComponent {
* @returns {Observable} Emits true whenever a given filter config should be shown
*/
isActive(filterConfig: SearchFilterConfig): Observable {
- // console.log(filter.name);
return this.filterService.getSelectedValuesForFilter(filterConfig).pipe(
mergeMap((isActive) => {
if (isNotEmpty(isActive)) {
diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html
index 653f5e8cd4..6476f8bd68 100644
--- a/src/app/+search-page/search-page.component.html
+++ b/src/app/+search-page/search-page.component.html
@@ -26,7 +26,7 @@
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.component.html b/src/app/+search-page/search-sidebar/search-sidebar.component.html
index 71959b558b..5ff1e3c8fa 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.component.html
+++ b/src/app/+search-page/search-sidebar/search-sidebar.component.html
@@ -4,7 +4,7 @@