diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts index 27daa30a0f..de932fab02 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts @@ -23,7 +23,7 @@ import { SearchConfigurationServiceStub } from '../shared/testing/search-configu import { SearchService } from '../+search-page/search-service/search.service'; import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service'; import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; -import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchFilterService } from '../+search-page/search-filters/search-filter/search-filter.service'; import { RoleDirective } from '../shared/roles/role.directive'; import { RoleService } from '../core/roles/role.service'; @@ -108,7 +108,7 @@ describe('MyDSpacePageComponent', () => { }) }, { - provide: SearchSidebarService, + provide: SidebarService, useValue: sidebarService }, { diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts index 251bf50bd1..bb43ed1183 100644 --- a/src/app/+my-dspace-page/my-dspace-page.component.ts +++ b/src/app/+my-dspace-page/my-dspace-page.component.ts @@ -17,7 +17,7 @@ import { pushInOut } from '../shared/animations/push'; import { HostWindowService } from '../shared/host-window.service'; import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model'; import { SearchService } from '../+search-page/search-service/search.service'; -import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { hasValue } from '../shared/empty.util'; import { getSucceededRemoteData } from '../core/shared/operators'; import { MyDSpaceResult } from './my-dspace-result.model'; @@ -96,7 +96,7 @@ export class MyDSpacePageComponent implements OnInit { viewModeList = [ViewMode.List, ViewMode.Detail]; constructor(private service: SearchService, - private sidebarService: SearchSidebarService, + private sidebarService: SidebarService, private windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) { this.isXsOrSm$ = this.windowService.isXsOrSm(); diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index b1a94fc086..dbe54de72f 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -1,6 +1,6 @@ import { HostWindowService } from '../shared/host-window.service'; import { SearchService } from './search-service/search.service'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; @@ -36,7 +36,7 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem @Input() configuration: string; constructor(protected service: SearchService, - protected sidebarService: SearchSidebarService, + protected sidebarService: SidebarService, protected windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, protected routeService: RouteService) { diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts index 0bcc9e14e3..5da23bd853 100644 --- a/src/app/+search-page/filtered-search-page.component.ts +++ b/src/app/+search-page/filtered-search-page.component.ts @@ -1,6 +1,6 @@ import { HostWindowService } from '../shared/host-window.service'; import { SearchService } from './search-service/search.service'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchPageComponent } from './search-page.component'; import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; import { pushInOut } from '../shared/animations/push'; @@ -38,7 +38,7 @@ export class FilteredSearchPageComponent extends SearchPageComponent implements @Input() fixedFilterQuery: string; constructor(protected service: SearchService, - protected sidebarService: SearchSidebarService, + protected sidebarService: SidebarService, protected windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, protected routeService: RouteService) { diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts index aefa5c145f..0ba1f88cc3 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.spec.ts @@ -138,7 +138,7 @@ describe('SearchFilterService', () => { service.expand(mockFilterConfig.name); }); - it('SearchSidebarExpandAction should be dispatched to the store', () => { + it('SidebarExpandAction should be dispatched to the store', () => { expect(store.dispatch).toHaveBeenCalledWith(new SearchFilterExpandAction(mockFilterConfig.name)); }); }); diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index 85ad8286bf..7efd22d5a9 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -1,43 +1,50 @@ -
-
- -
- - - -
-
- - -
-
- - -
- -
-
-
-
+
+
+
+
+
+ +
+
+ +
+
+
+ + +
+ +
+
+
+ + + + + + + + + + + + diff --git a/src/app/+search-page/search-page.component.scss b/src/app/+search-page/search-page.component.scss index 05abf74f05..9c3da88be6 100644 --- a/src/app/+search-page/search-page.component.scss +++ b/src/app/+search-page/search-page.component.scss @@ -1,52 +1,10 @@ - @include media-breakpoint-down(md) { - .container { - width: 100%; - max-width: none; - } + .container { + width: 100%; + max-width: none; + } } /deep/ .search-controls { margin-bottom: $spacer; } - -#search-body { - &.row-offcanvas { - width: 100%; - } - @include media-breakpoint-down(sm) { - position: relative; - - &.row-offcanvas { - position: relative; - } - - &.row-offcanvas-right #search-sidebar-sm { - right: -100%; - } - - &.row-offcanvas-left #search-sidebar-sm { - left: -100%; - } - - #search-sidebar-sm { - position: absolute; - top: 0; - width: 100%; - } - } -} - -@include media-breakpoint-up(md) { - .sidebar-md-sticky { - position: sticky; - position: -webkit-sticky; - top: 0; - z-index: $zindex-sticky; - padding-top: $content-spacing; - margin-top: -$content-spacing; - align-self: flex-start; - display: block; - } -} - diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index d072c80628..9f17beeab3 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -16,7 +16,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { ActivatedRoute } from '@angular/router'; import { By } from '@angular/platform-browser'; import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { SearchFilterService } from './search-filters/search-filter/search-filter.service'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { RemoteData } from '../core/data/remote-data'; @@ -115,7 +115,7 @@ export function configureSearchComponentTestingModule(compType) { }) }, { - provide: SearchSidebarService, + provide: SidebarService, useValue: sidebarService }, { @@ -191,34 +191,4 @@ describe('SearchPageComponent', () => { }); }); - - describe('when sidebarCollapsed is true in mobile view', () => { - let menu: HTMLElement; - - beforeEach(() => { - menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement; - (comp as any).isSidebarCollapsed$ = observableOf(true); - fixture.detectChanges(); - }); - - it('should close the sidebar', () => { - expect(menu.classList).not.toContain('active'); - }); - - }); - - describe('when sidebarCollapsed is false in mobile view', () => { - let menu: HTMLElement; - - beforeEach(() => { - menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement; - (comp as any).isSidebarCollapsed$ = observableOf(false); - fixture.detectChanges(); - }); - - it('should open the menu', () => { - expect(menu.classList).toContain('active'); - }); - - }); }); diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 0558065142..bf0c2e3e73 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -9,7 +9,7 @@ import { HostWindowService } from '../shared/host-window.service'; import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SearchResult } from './search-result.model'; import { SearchService } from './search-service/search.service'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; import { hasValue, isNotEmpty } from '../shared/empty.util'; import { SearchConfigurationService } from './search-service/search-configuration.service'; import { getSucceededRemoteData } from '../core/shared/operators'; @@ -102,7 +102,7 @@ export class SearchPageComponent implements OnInit { isSidebarCollapsed$: Observable; constructor(protected service: SearchService, - protected sidebarService: SearchSidebarService, + protected sidebarService: SidebarService, protected windowService: HostWindowService, @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, protected routeService: RouteService) { diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 6ca449460b..a500cc42d7 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -9,8 +9,8 @@ import { ItemSearchResultGridElementComponent } from '../shared/object-grid/sear import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component' import { CollectionSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component'; import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; -import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects'; +import { SidebarService } from '../shared/sidebar/sidebar.service'; +import { SidebarEffects } from '../shared/sidebar/sidebar-effects.service'; import { SearchSettingsComponent } from './search-settings/search-settings.component'; import { EffectsModule } from '@ngrx/effects'; import { SearchFiltersComponent } from './search-filters/search-filters.component'; @@ -36,7 +36,7 @@ import { ConfigurationSearchPageGuard } from './configuration-search-page.guard' import { FilteredSearchPageComponent } from './filtered-search-page.component'; const effects = [ - SearchSidebarEffects + SidebarEffects ]; const components = [ @@ -73,11 +73,11 @@ const components = [ CommonModule, SharedModule, EffectsModule.forFeature(effects), - CoreModule.forRoot() + CoreModule.forRoot(), ], declarations: components, providers: [ - SearchSidebarService, + SidebarService, SearchFilterService, SearchFixedFilterService, ConfigurationSearchPageGuard, diff --git a/src/app/+search-page/search-settings/search-settings.component.spec.ts b/src/app/+search-page/search-settings/search-settings.component.spec.ts index b9b5c5a5eb..729c228f44 100644 --- a/src/app/+search-page/search-settings/search-settings.component.spec.ts +++ b/src/app/+search-page/search-settings/search-settings.component.spec.ts @@ -7,7 +7,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute } from '@angular/router'; -import { SearchSidebarService } from '../search-sidebar/search-sidebar.service'; +import { SidebarService } from '../../shared/sidebar/sidebar.service'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { By } from '@angular/platform-browser'; @@ -65,7 +65,7 @@ describe('SearchSettingsComponent', () => { { provide: ActivatedRoute, useValue: activatedRouteStub }, { - provide: SearchSidebarService, + provide: SidebarService, useValue: sidebarService }, { diff --git a/src/app/+search-page/search-sidebar/search-sidebar.reducer.ts b/src/app/+search-page/search-sidebar/search-sidebar.reducer.ts deleted file mode 100644 index a01f0ff6d6..0000000000 --- a/src/app/+search-page/search-sidebar/search-sidebar.reducer.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { SearchSidebarAction, SearchSidebarActionTypes } from './search-sidebar.actions'; - -/** - * Interface that represents the state of the sidebar - */ -export interface SearchSidebarState { - sidebarCollapsed: boolean; -} - -const initialState: SearchSidebarState = { - sidebarCollapsed: true -}; - -/** - * Performs a search sidebar action on the current state - * @param {SearchSidebarState} state The state before the action is performed - * @param {SearchSidebarAction} action The action that should be performed - * @returns {SearchSidebarState} The state after the action is performed - */ -export function sidebarReducer(state = initialState, action: SearchSidebarAction): SearchSidebarState { - switch (action.type) { - - case SearchSidebarActionTypes.COLLAPSE: { - return Object.assign({}, state, { - sidebarCollapsed: true - }); - } - - case SearchSidebarActionTypes.EXPAND: { - return Object.assign({}, state, { - sidebarCollapsed: false - }); - - } - - case SearchSidebarActionTypes.TOGGLE: { - return Object.assign({}, state, { - sidebarCollapsed: !state.sidebarCollapsed - }); - - } - - default: { - return state; - } - } -} diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index bc84f961fb..fbdce542a8 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -3,9 +3,9 @@ import * as fromRouter from '@ngrx/router-store'; import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; import { formReducer, FormState } from './shared/form/form.reducer'; import { - SearchSidebarState, + SidebarState, sidebarReducer -} from './+search-page/search-sidebar/search-sidebar.reducer'; +} from './shared/sidebar/sidebar.reducer'; import { filterReducer, SearchFiltersState @@ -37,7 +37,7 @@ export interface AppState { metadataRegistry: MetadataRegistryState; bitstreamFormats: BitstreamFormatRegistryState; notifications: NotificationsState; - searchSidebar: SearchSidebarState; + searchSidebar: SidebarState; searchFilter: SearchFiltersState; truncatable: TruncatablesState; cssVariables: CSSVariablesState; diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index ed9f8efa93..5a1c2de26f 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -147,6 +147,7 @@ import { TypedItemSearchResultGridElementComponent } from './object-grid/item-gr import { PublicationGridElementComponent } from './object-grid/item-grid-element/item-types/publication/publication-grid-element.component'; import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component'; import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component'; +import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -233,6 +234,7 @@ const COMPONENTS = [ ObjectCollectionComponent, PaginationComponent, SearchFormComponent, + PageWithSidebarComponent, ThumbnailComponent, GridThumbnailComponent, UploaderComponent, diff --git a/src/app/shared/sidebar/page-with-sidebar.component.html b/src/app/shared/sidebar/page-with-sidebar.component.html new file mode 100644 index 0000000000..9feb6c792e --- /dev/null +++ b/src/app/shared/sidebar/page-with-sidebar.component.html @@ -0,0 +1,14 @@ +
+
+
+ +
+ +
+
+
+
diff --git a/src/app/shared/sidebar/page-with-sidebar.component.scss b/src/app/shared/sidebar/page-with-sidebar.component.scss new file mode 100644 index 0000000000..8be48cea2b --- /dev/null +++ b/src/app/shared/sidebar/page-with-sidebar.component.scss @@ -0,0 +1,52 @@ +@include media-breakpoint-down(md) { + .container { + width: 100%; + max-width: none; + } +} + +.row-with-sidebar { + + &.row-offcanvas { + width: 100%; + } + + @include media-breakpoint-up(md) { + display: flex; + } + + @include media-breakpoint-down(sm) { + position: relative; + + &.row-offcanvas { + position: relative; + } + + &.row-offcanvas-right .sidebar-content { + right: -100%; + } + + &.row-offcanvas-left .sidebar-content { + left: -100%; + } + + .sidebar-content { + position: absolute; + top: 0; + width: 100%; + } + } +} + +@include media-breakpoint-up(md) { + .sidebar-content { + position: sticky; + position: -webkit-sticky; + top: 0; + z-index: $zindex-sticky; + padding-top: $content-spacing; + margin-top: -$content-spacing; + align-self: flex-start; + display: block; + } +} diff --git a/src/app/shared/sidebar/page-with-sidebar.component.spec.ts b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts new file mode 100644 index 0000000000..dfe035d2be --- /dev/null +++ b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts @@ -0,0 +1,75 @@ +import { By } from '@angular/platform-browser'; +import { of as observableOf } from 'rxjs'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { PageWithSidebarComponent } from './page-with-sidebar.component'; +import { SidebarService } from './sidebar/sidebar.service'; +import { HostWindowService } from '../shared/host-window.service'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; + +describe('PageWithSidebarComponent', () => { + let comp:PageWithSidebarComponent; + let fixture:ComponentFixture; + + const sidebarService = { + isCollapsed: observableOf(true), + collapse: () => this.isCollapsed = observableOf(true), + expand: () => this.isCollapsed = observableOf(false) + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [NoopAnimationsModule], + providers: [ + { + provide: SidebarService, + useValue: sidebarService + }, + { + provide: HostWindowService, useValue: jasmine.createSpyObj('hostWindowService', + { + isXs: observableOf(true), + isSm: observableOf(false), + isXsOrSm: observableOf(true) + }) + }, + ], + declarations: [PageWithSidebarComponent] + }).compileComponents(); + fixture = TestBed.createComponent(PageWithSidebarComponent); + comp = fixture.componentInstance; + comp.id = 'mock-id'; + fixture.detectChanges(); + }); + + describe('when sidebarCollapsed is true in mobile view', () => { + let menu:HTMLElement; + + beforeEach(() => { + menu = fixture.debugElement.query(By.css('#mock-id-sidebar-content')).nativeElement; + (comp as any).sidebarService.isCollapsed = observableOf(true); + comp.ngOnInit(); + fixture.detectChanges(); + }); + + it('should close the sidebar', () => { + expect(menu.classList).not.toContain('active'); + }); + + }); + + describe('when sidebarCollapsed is false in mobile view', () => { + let menu:HTMLElement; + + beforeEach(() => { + menu = fixture.debugElement.query(By.css('#mock-id-sidebar-content')).nativeElement; + (comp as any).sidebarService.isCollapsed = observableOf(false); + comp.ngOnInit(); + fixture.detectChanges(); + }); + + it('should open the menu', () => { + expect(menu.classList).toContain('active'); + }); + + }); +}); diff --git a/src/app/shared/sidebar/page-with-sidebar.component.ts b/src/app/shared/sidebar/page-with-sidebar.component.ts new file mode 100644 index 0000000000..8020d492ec --- /dev/null +++ b/src/app/shared/sidebar/page-with-sidebar.component.ts @@ -0,0 +1,71 @@ +import { Component, Input, OnInit, TemplateRef } from '@angular/core'; +import { SidebarService } from './sidebar.service'; +import { HostWindowService } from '../host-window.service'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { pushInOut } from '../animations/push'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'ds-page-with-sidebar', + styleUrls: ['./page-with-sidebar.component.scss'], + templateUrl: './page-with-sidebar.component.html', + animations: [pushInOut], +}) +export class PageWithSidebarComponent implements OnInit { + @Input() id:string; + @Input() sidebarContent:TemplateRef; + + /** + * Emits true if were on a small screen + */ + isXsOrSm$:Observable; + + /** + * The width of the sidebar (bootstrap columns) + */ + @Input() + sideBarWidth = 3; + + /** + * Observable for whether or not the sidebar is currently collapsed + */ + isSidebarCollapsed$:Observable; + + sidebarClasses:Observable; + + constructor(protected sidebarService:SidebarService, + protected windowService:HostWindowService, + ) { + } + + ngOnInit():void { + this.isXsOrSm$ = this.windowService.isXsOrSm(); + this.isSidebarCollapsed$ = this.isSidebarCollapsed(); + this.sidebarClasses = this.isSidebarCollapsed$.pipe( + map((isCollapsed) => isCollapsed ? '' : 'active') + ); + } + + /** + * Check if the sidebar is collapsed + * @returns {Observable} emits true if the sidebar is currently collapsed, false if it is expanded + */ + private isSidebarCollapsed():Observable { + return this.sidebarService.isCollapsed; + } + + /** + * Set the sidebar to a collapsed state + */ + public closeSidebar():void { + this.sidebarService.collapse() + } + + /** + * Set the sidebar to an expanded state + */ + public openSidebar():void { + this.sidebarService.expand(); + } + +} diff --git a/src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts b/src/app/shared/sidebar/search-sidebar.effects.spec.ts similarity index 68% rename from src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts rename to src/app/shared/sidebar/search-sidebar.effects.spec.ts index f34f6b72de..da675a38ce 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts +++ b/src/app/shared/sidebar/search-sidebar.effects.spec.ts @@ -3,24 +3,24 @@ import { Observable } from 'rxjs'; import { provideMockActions } from '@ngrx/effects/testing'; import { cold, hot } from 'jasmine-marbles'; import * as fromRouter from '@ngrx/router-store'; -import { SearchSidebarCollapseAction } from './search-sidebar.actions'; -import { SearchSidebarEffects } from './search-sidebar.effects'; +import { SidebarCollapseAction } from './sidebar.actions'; +import { SidebarEffects } from './sidebar-effects.service'; -describe('SearchSidebarEffects', () => { - let sidebarEffects: SearchSidebarEffects; +describe('SidebarEffects', () => { + let sidebarEffects: SidebarEffects; let actions: Observable; const dummyURL = 'http://f4fb15e2-1bd3-4e63-8d0d-486ad8bc714a'; beforeEach(() => { TestBed.configureTestingModule({ providers: [ - SearchSidebarEffects, + SidebarEffects, provideMockActions(() => actions), // other providers ], }); - sidebarEffects = TestBed.get(SearchSidebarEffects); + sidebarEffects = TestBed.get(SidebarEffects); }); describe('routeChange$', () => { @@ -28,7 +28,7 @@ describe('SearchSidebarEffects', () => { it('should return a COLLAPSE action in response to an UPDATE_LOCATION action to a new route', () => { actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION, payload: {routerState: {url: dummyURL}} } }); - const expected = cold('--b-', { b: new SearchSidebarCollapseAction() }); + const expected = cold('--b-', { b: new SidebarCollapseAction() }); expect(sidebarEffects.routeChange$).toBeObservable(expected); }); diff --git a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts b/src/app/shared/sidebar/sidebar-effects.service.ts similarity index 85% rename from src/app/+search-page/search-sidebar/search-sidebar.effects.ts rename to src/app/shared/sidebar/sidebar-effects.service.ts index 1f5fb0ef60..fc6643be4b 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts +++ b/src/app/shared/sidebar/sidebar-effects.service.ts @@ -3,14 +3,14 @@ import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects' import * as fromRouter from '@ngrx/router-store'; -import { SearchSidebarCollapseAction } from './search-sidebar.actions'; +import { SidebarCollapseAction } from './sidebar.actions'; import { URLBaser } from '../../core/url-baser/url-baser'; /** * Makes sure that if the user navigates to another route, the sidebar is collapsed */ @Injectable() -export class SearchSidebarEffects { +export class SidebarEffects { private previousPath: string; @Effect() routeChange$ = this.actions$ .pipe( @@ -19,7 +19,7 @@ export class SearchSidebarEffects { tap((action) => { this.previousPath = this.getBaseUrl(action) }), - map(() => new SearchSidebarCollapseAction()) + map(() => new SidebarCollapseAction()) ); constructor(private actions$: Actions) { diff --git a/src/app/+search-page/search-sidebar/search-sidebar.actions.ts b/src/app/shared/sidebar/sidebar.actions.ts similarity index 51% rename from src/app/+search-page/search-sidebar/search-sidebar.actions.ts rename to src/app/shared/sidebar/sidebar.actions.ts index 84a34b2790..4a7c85696a 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.actions.ts +++ b/src/app/shared/sidebar/sidebar.actions.ts @@ -1,6 +1,6 @@ import { Action } from '@ngrx/store'; -import { type } from '../../shared/ngrx/type'; +import { type } from '../ngrx/type'; /** * For each action type in an action group, make a simple @@ -10,32 +10,32 @@ import { type } from '../../shared/ngrx/type'; * literal types and runs a simple check to guarantee all * action types in the application are unique. */ -export const SearchSidebarActionTypes = { - COLLAPSE: type('dspace/search-sidebar/COLLAPSE'), - EXPAND: type('dspace/search-sidebar/EXPAND'), - TOGGLE: type('dspace/search-sidebar/TOGGLE') +export const SidebarActionTypes = { + COLLAPSE: type('dspace/sidebar/COLLAPSE'), + EXPAND: type('dspace/sidebar/EXPAND'), + TOGGLE: type('dspace/sidebar/TOGGLE') }; /* tslint:disable:max-classes-per-file */ /** * Used to collapse the sidebar */ -export class SearchSidebarCollapseAction implements Action { - type = SearchSidebarActionTypes.COLLAPSE; +export class SidebarCollapseAction implements Action { + type = SidebarActionTypes.COLLAPSE; } /** * Used to expand the sidebar */ -export class SearchSidebarExpandAction implements Action { - type = SearchSidebarActionTypes.EXPAND; +export class SidebarExpandAction implements Action { + type = SidebarActionTypes.EXPAND; } /** * Used to collapse the sidebar when it's expanded and expand it when it's collapsed */ -export class SearchSidebarToggleAction implements Action { - type = SearchSidebarActionTypes.TOGGLE; +export class SidebarToggleAction implements Action { + type = SidebarActionTypes.TOGGLE; } /* tslint:enable:max-classes-per-file */ @@ -43,7 +43,7 @@ export class SearchSidebarToggleAction implements Action { * Export a type alias of all actions in this action group * so that reducers can easily compose action types */ -export type SearchSidebarAction - = SearchSidebarCollapseAction - | SearchSidebarExpandAction - | SearchSidebarToggleAction +export type SidebarAction + = SidebarCollapseAction + | SidebarExpandAction + | SidebarToggleAction diff --git a/src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts b/src/app/shared/sidebar/sidebar.reducer.spec.ts similarity index 80% rename from src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts rename to src/app/shared/sidebar/sidebar.reducer.spec.ts index dfdead4369..0fbfce84fc 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts +++ b/src/app/shared/sidebar/sidebar.reducer.spec.ts @@ -1,12 +1,12 @@ import * as deepFreeze from 'deep-freeze'; -import { sidebarReducer } from './search-sidebar.reducer'; +import { sidebarReducer } from './sidebar.reducer'; import { - SearchSidebarCollapseAction, SearchSidebarExpandAction, - SearchSidebarToggleAction -} from './search-sidebar.actions'; + SidebarCollapseAction, SidebarExpandAction, + SidebarToggleAction +} from './sidebar.actions'; -class NullAction extends SearchSidebarCollapseAction { +class NullAction extends SidebarCollapseAction { type = null; constructor() { @@ -34,7 +34,7 @@ describe('sidebarReducer', () => { it('should set sidebarCollapsed to true in response to the COLLAPSE action', () => { const state = { sidebarCollapsed: false }; - const action = new SearchSidebarCollapseAction(); + const action = new SidebarCollapseAction(); const newState = sidebarReducer(state, action); expect(newState.sidebarCollapsed).toEqual(true); @@ -44,7 +44,7 @@ describe('sidebarReducer', () => { const state = { sidebarCollapsed: false }; deepFreeze([state]); - const action = new SearchSidebarCollapseAction(); + const action = new SidebarCollapseAction(); sidebarReducer(state, action); // no expect required, deepFreeze will ensure an exception is thrown if the state @@ -53,7 +53,7 @@ describe('sidebarReducer', () => { it('should set sidebarCollapsed to false in response to the EXPAND action', () => { const state = { sidebarCollapsed: true }; - const action = new SearchSidebarExpandAction(); + const action = new SidebarExpandAction(); const newState = sidebarReducer(state, action); expect(newState.sidebarCollapsed).toEqual(false); @@ -63,13 +63,13 @@ describe('sidebarReducer', () => { const state = { sidebarCollapsed: true }; deepFreeze([state]); - const action = new SearchSidebarExpandAction(); + const action = new SidebarExpandAction(); sidebarReducer(state, action); }); it('should flip the value of sidebarCollapsed in response to the TOGGLE action', () => { const state1 = { sidebarCollapsed: true }; - const action = new SearchSidebarToggleAction(); + const action = new SidebarToggleAction(); const state2 = sidebarReducer(state1, action); const state3 = sidebarReducer(state2, action); @@ -82,7 +82,7 @@ describe('sidebarReducer', () => { const state = { sidebarCollapsed: true }; deepFreeze([state]); - const action = new SearchSidebarToggleAction(); + const action = new SidebarToggleAction(); sidebarReducer(state, action); }); diff --git a/src/app/shared/sidebar/sidebar.reducer.ts b/src/app/shared/sidebar/sidebar.reducer.ts new file mode 100644 index 0000000000..3c3dc63b0d --- /dev/null +++ b/src/app/shared/sidebar/sidebar.reducer.ts @@ -0,0 +1,47 @@ +import { SidebarAction, SidebarActionTypes } from './sidebar.actions'; + +/** + * Interface that represents the state of the sidebar + */ +export interface SidebarState { + sidebarCollapsed: boolean; +} + +const initialState: SidebarState = { + sidebarCollapsed: true +}; + +/** + * Performs a search sidebar action on the current state + * @param {SidebarState} state The state before the action is performed + * @param {SidebarAction} action The action that should be performed + * @returns {SidebarState} The state after the action is performed + */ +export function sidebarReducer(state = initialState, action: SidebarAction): SidebarState { + switch (action.type) { + + case SidebarActionTypes.COLLAPSE: { + return Object.assign({}, state, { + sidebarCollapsed: true + }); + } + + case SidebarActionTypes.EXPAND: { + return Object.assign({}, state, { + sidebarCollapsed: false + }); + + } + + case SidebarActionTypes.TOGGLE: { + return Object.assign({}, state, { + sidebarCollapsed: !state.sidebarCollapsed + }); + + } + + default: { + return state; + } + } +} diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts b/src/app/shared/sidebar/sidebar.service.spec.ts similarity index 61% rename from src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts rename to src/app/shared/sidebar/sidebar.service.spec.ts index 0cccf9ea40..6d8e845836 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts +++ b/src/app/shared/sidebar/sidebar.service.spec.ts @@ -1,13 +1,13 @@ import { Store } from '@ngrx/store'; -import { SearchSidebarService } from './search-sidebar.service'; +import { SidebarService } from './sidebar.service'; import { AppState } from '../../app.reducer'; import { async, TestBed } from '@angular/core/testing'; import { of as observableOf } from 'rxjs'; -import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions'; -import { HostWindowService } from '../../shared/host-window.service'; +import { SidebarCollapseAction, SidebarExpandAction } from './sidebar.actions'; +import { HostWindowService } from '../host-window.service'; -describe('SearchSidebarService', () => { - let service: SearchSidebarService; +describe('SidebarService', () => { + let service: SidebarService; const store: Store = jasmine.createSpyObj('store', { /* tslint:disable:no-empty */ dispatch: {}, @@ -35,7 +35,7 @@ describe('SearchSidebarService', () => { })); beforeEach(() => { - service = new SearchSidebarService(store, windowService); + service = new SidebarService(store, windowService); }) ; describe('when the collapse method is triggered', () => { @@ -43,8 +43,8 @@ describe('SearchSidebarService', () => { service.collapse(); }); - it('SearchSidebarCollapseAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarCollapseAction()); + it('SidebarCollapseAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SidebarCollapseAction()); }); }); @@ -54,8 +54,8 @@ describe('SearchSidebarService', () => { service.expand(); }); - it('SearchSidebarExpandAction should be dispatched to the store', () => { - expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarExpandAction()); + it('SidebarExpandAction should be dispatched to the store', () => { + expect(store.dispatch).toHaveBeenCalledWith(new SidebarExpandAction()); }); }); diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.ts b/src/app/shared/sidebar/sidebar.service.ts similarity index 77% rename from src/app/+search-page/search-sidebar/search-sidebar.service.ts rename to src/app/shared/sidebar/sidebar.service.ts index 7185984538..2e3303887b 100644 --- a/src/app/+search-page/search-sidebar/search-sidebar.service.ts +++ b/src/app/shared/sidebar/sidebar.service.ts @@ -1,20 +1,20 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { Injectable } from '@angular/core'; -import { SearchSidebarState } from './search-sidebar.reducer'; +import { SidebarState } from './sidebar.reducer'; import { createSelector, select, Store } from '@ngrx/store'; -import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions'; +import { SidebarCollapseAction, SidebarExpandAction } from './sidebar.actions'; import { AppState } from '../../app.reducer'; -import { HostWindowService } from '../../shared/host-window.service'; +import { HostWindowService } from '../host-window.service'; import { map } from 'rxjs/operators'; const sidebarStateSelector = (state: AppState) => state.searchSidebar; -const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SearchSidebarState) => sidebar.sidebarCollapsed); +const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SidebarState) => sidebar.sidebarCollapsed); /** * Service that performs all actions that have to do with the search sidebar */ @Injectable() -export class SearchSidebarService { +export class SidebarService { /** * Emits true is the current screen size is mobile */ @@ -47,13 +47,13 @@ export class SearchSidebarService { * Dispatches a collapse action to the store */ public collapse(): void { - this.store.dispatch(new SearchSidebarCollapseAction()); + this.store.dispatch(new SidebarCollapseAction()); } /** * Dispatches an expand action to the store */ public expand(): void { - this.store.dispatch(new SearchSidebarExpandAction()); + this.store.dispatch(new SidebarExpandAction()); } }