From 4f7b7637cb0669c927bf3eb43503cfea3797fd29 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Tue, 22 Oct 2019 16:47:03 +0200 Subject: [PATCH 1/5] Refactor search sidebar --- .../my-dspace-page.component.spec.ts | 4 +- .../my-dspace-page.component.ts | 4 +- .../configuration-search-page.component.ts | 4 +- .../filtered-search-page.component.ts | 4 +- .../search-filter.service.spec.ts | 2 +- .../+search-page/search-page.component.html | 89 ++++++++++--------- .../+search-page/search-page.component.scss | 50 +---------- .../search-page.component.spec.ts | 34 +------ src/app/+search-page/search-page.component.ts | 4 +- src/app/+search-page/search-page.module.ts | 10 +-- .../search-settings.component.spec.ts | 4 +- .../search-sidebar/search-sidebar.reducer.ts | 47 ---------- src/app/app.reducer.ts | 6 +- src/app/shared/shared.module.ts | 2 + .../sidebar/page-with-sidebar.component.html | 14 +++ .../sidebar/page-with-sidebar.component.scss | 52 +++++++++++ .../page-with-sidebar.component.spec.ts | 75 ++++++++++++++++ .../sidebar/page-with-sidebar.component.ts | 71 +++++++++++++++ .../sidebar}/search-sidebar.effects.spec.ts | 14 +-- .../sidebar/sidebar-effects.service.ts} | 6 +- .../sidebar/sidebar.actions.ts} | 30 +++---- .../sidebar/sidebar.reducer.spec.ts} | 22 ++--- src/app/shared/sidebar/sidebar.reducer.ts | 47 ++++++++++ .../sidebar/sidebar.service.spec.ts} | 20 ++--- .../sidebar/sidebar.service.ts} | 14 +-- 25 files changed, 389 insertions(+), 240 deletions(-) delete mode 100644 src/app/+search-page/search-sidebar/search-sidebar.reducer.ts create mode 100644 src/app/shared/sidebar/page-with-sidebar.component.html create mode 100644 src/app/shared/sidebar/page-with-sidebar.component.scss create mode 100644 src/app/shared/sidebar/page-with-sidebar.component.spec.ts create mode 100644 src/app/shared/sidebar/page-with-sidebar.component.ts rename src/app/{+search-page/search-sidebar => shared/sidebar}/search-sidebar.effects.spec.ts (68%) rename src/app/{+search-page/search-sidebar/search-sidebar.effects.ts => shared/sidebar/sidebar-effects.service.ts} (85%) rename src/app/{+search-page/search-sidebar/search-sidebar.actions.ts => shared/sidebar/sidebar.actions.ts} (51%) rename src/app/{+search-page/search-sidebar/search-sidebar.reducer.spec.ts => shared/sidebar/sidebar.reducer.spec.ts} (80%) create mode 100644 src/app/shared/sidebar/sidebar.reducer.ts rename src/app/{+search-page/search-sidebar/search-sidebar.service.spec.ts => shared/sidebar/sidebar.service.spec.ts} (61%) rename src/app/{+search-page/search-sidebar/search-sidebar.service.ts => shared/sidebar/sidebar.service.ts} (77%) 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()); } } From 771041a3cf8616631596c9a23c0bf06b28096ac2 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Fri, 8 Nov 2019 14:03:58 +0100 Subject: [PATCH 2/5] Implement sidebar filter and dropdown components --- .../search-filter.component.html | 21 +++-- src/app/+search-page/search-page.module.ts | 2 + .../search-settings.component.html | 52 ++++++----- src/app/app.reducer.ts | 12 ++- src/app/shared/shared.module.ts | 6 ++ ...ebar-filter-selected-option.component.html | 6 ++ ...ebar-filter-selected-option.component.scss | 11 +++ ...idebar-filter-selected-option.component.ts | 15 ++++ .../sidebar/filter/sidebar-filter.actions.ts | 74 ++++++++++++++++ .../filter/sidebar-filter.component.html | 24 +++++ .../filter/sidebar-filter.component.scss | 12 +++ .../filter/sidebar-filter.component.ts | 84 ++++++++++++++++++ .../sidebar/filter/sidebar-filter.reducer.ts | 70 +++++++++++++++ .../sidebar/filter/sidebar-filter.service.ts | 87 +++++++++++++++++++ .../page-with-sidebar.component.spec.ts | 4 +- .../sidebar/sidebar-dropdown.component.html | 6 ++ .../sidebar/sidebar-dropdown.component.scss | 3 + .../sidebar/sidebar-dropdown.component.ts | 12 +++ src/app/shared/sidebar/sidebar.reducer.ts | 2 +- src/app/shared/sidebar/sidebar.service.ts | 2 +- 20 files changed, 471 insertions(+), 34 deletions(-) create mode 100644 src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html create mode 100644 src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss create mode 100644 src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.actions.ts create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.component.html create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.component.scss create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.component.ts create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.reducer.ts create mode 100644 src/app/shared/sidebar/filter/sidebar-filter.service.ts create mode 100644 src/app/shared/sidebar/sidebar-dropdown.component.html create mode 100644 src/app/shared/sidebar/sidebar-dropdown.component.scss create mode 100644 src/app/shared/sidebar/sidebar-dropdown.component.ts 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 a1758d7339..a03a0de451 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,7 +1,18 @@
-
{{'search.filters.filter.' + filter.name + '.head'| translate}}
-
- -
+
+
+ {{'search.filters.filter.' + filter.name + '.head'| translate}} +
+ + +
+
+ + +
diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index a500cc42d7..8d583a78af 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -34,6 +34,7 @@ import { SearchLabelComponent } from './search-labels/search-label/search-label. import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { FilteredSearchPageComponent } from './filtered-search-page.component'; +import { SidebarFilterService } from "../shared/sidebar/filter/sidebar-filter.service"; const effects = [ SidebarEffects @@ -78,6 +79,7 @@ const components = [ declarations: components, providers: [ SidebarService, + SidebarFilterService, SearchFilterService, SearchFixedFilterService, ConfigurationSearchPageGuard, diff --git a/src/app/+search-page/search-settings/search-settings.component.html b/src/app/+search-page/search-settings/search-settings.component.html index d693196dae..d8878982b4 100644 --- a/src/app/+search-page/search-settings/search-settings.component.html +++ b/src/app/+search-page/search-settings/search-settings.component.html @@ -1,24 +1,32 @@ -

{{ 'search.sidebar.settings.title' | translate}}

-
-
{{ 'search.sidebar.settings.sort-by' | translate}}
- -
+

{{ 'search.sidebar.settings.title' | translate}}

-
-
{{ 'search.sidebar.settings.rpp' | translate}}
- -
-
\ No newline at end of file +
+ + + +
+ +
+ + + +
+ diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index fbdce542a8..f9dc813722 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,4 +1,4 @@ -import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store'; +import { ActionReducerMap, createSelector, MemoizedSelector, State } from '@ngrx/store'; import * as fromRouter from '@ngrx/router-store'; import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; import { formReducer, FormState } from './shared/form/form.reducer'; @@ -6,6 +6,10 @@ import { SidebarState, sidebarReducer } from './shared/sidebar/sidebar.reducer'; +import { + SidebarFilterState, + sidebarFilterReducer, SidebarFiltersState +} from './shared/sidebar/filter/sidebar-filter.reducer'; import { filterReducer, SearchFiltersState @@ -37,7 +41,8 @@ export interface AppState { metadataRegistry: MetadataRegistryState; bitstreamFormats: BitstreamFormatRegistryState; notifications: NotificationsState; - searchSidebar: SidebarState; + sidebar: SidebarState; + sidebarFilter: SidebarFiltersState; searchFilter: SearchFiltersState; truncatable: TruncatablesState; cssVariables: CSSVariablesState; @@ -53,7 +58,8 @@ export const appReducers: ActionReducerMap = { metadataRegistry: metadataRegistryReducer, bitstreamFormats: bitstreamFormatReducer, notifications: notificationsReducer, - searchSidebar: sidebarReducer, + sidebar: sidebarReducer, + sidebarFilter: sidebarFilterReducer, searchFilter: filterReducer, truncatable: truncatableReducer, cssVariables: cssVariablesReducer, diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 5a1c2de26f..0d427be49c 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -148,6 +148,9 @@ import { PublicationGridElementComponent } from './object-grid/item-grid-element 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'; +import { SidebarDropdownComponent } from './sidebar/sidebar-dropdown.component'; +import { SidebarFilterComponent } from './sidebar/filter/sidebar-filter.component'; +import { SidebarFilterSelectedOptionComponent } from './sidebar/filter/sidebar-filter-selected-option.component'; const MODULES = [ // Do NOT include UniversalModule, HttpModule, or JsonpModule here @@ -235,6 +238,9 @@ const COMPONENTS = [ PaginationComponent, SearchFormComponent, PageWithSidebarComponent, + SidebarDropdownComponent, + SidebarFilterComponent, + SidebarFilterSelectedOptionComponent, ThumbnailComponent, GridThumbnailComponent, UploaderComponent, diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html new file mode 100644 index 0000000000..bbe0b93566 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.html @@ -0,0 +1,6 @@ + + + diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss new file mode 100644 index 0000000000..b4e9cd340c --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.scss @@ -0,0 +1,11 @@ +a { + color: $body-color; + + &:hover, &focus { + text-decoration: none; + } + + span.badge { + vertical-align: text-top; + } +} diff --git a/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts new file mode 100644 index 0000000000..5c80a9cd87 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter-selected-option.component.ts @@ -0,0 +1,15 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'ds-sidebar-filter-selected-option', + styleUrls: ['./sidebar-filter-selected-option.component.scss'], + templateUrl: './sidebar-filter-selected-option.component.html', +}) + +/** + * Represents a single selected option in a sidebar filter + */ +export class SidebarFilterSelectedOptionComponent { + @Input() label:string; + @Output() click:EventEmitter = new EventEmitter(); +} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.actions.ts b/src/app/shared/sidebar/filter/sidebar-filter.actions.ts new file mode 100644 index 0000000000..2391274489 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.actions.ts @@ -0,0 +1,74 @@ +import { Action } from '@ngrx/store'; + +import { type } from '../../ngrx/type'; + +/** + * For each action type in an action group, make a simple + * enum object for all of this group's action types. + * + * The 'type' utility function coerces strings into string + * literal types and runs a simple check to guarantee all + * action types in the application are unique. + */ +export const SidebarFilterActionTypes = { + INITIALIZE: type('dspace/sidebar-filter/INITIALIZE'), + COLLAPSE: type('dspace/sidebar-filter/COLLAPSE'), + EXPAND: type('dspace/sidebar-filter/EXPAND'), + TOGGLE: type('dspace/sidebar-filter/TOGGLE'), +}; + +export class SidebarFilterAction implements Action { + /** + * Name of the filter the action is performed on, used to identify the filter + */ + filterName: string; + + /** + * Type of action that will be performed + */ + type; + + /** + * Initialize with the filter's name + * @param {string} name of the filter + */ + constructor(name: string) { + this.filterName = name; + } +} + +/* tslint:disable:max-classes-per-file */ +/** + * Used to initialize a filter + */ +export class FilterInitializeAction extends SidebarFilterAction { + type = SidebarFilterActionTypes.INITIALIZE; + initiallyExpanded; + + constructor(name:string, initiallyExpanded:boolean) { + super(name); + this.initiallyExpanded = initiallyExpanded; + } +} + +/** + * Used to collapse a filter + */ +export class FilterCollapseAction extends SidebarFilterAction { + type = SidebarFilterActionTypes.COLLAPSE; +} + +/** + * Used to expand a filter + */ +export class FilterExpandAction extends SidebarFilterAction { + type = SidebarFilterActionTypes.EXPAND; +} + +/** + * Used to collapse a filter when it's expanded and expand it when it's collapsed + */ +export class FilterToggleAction extends SidebarFilterAction { + type = SidebarFilterActionTypes.TOGGLE; +} +/* tslint:enable:max-classes-per-file */ diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.html b/src/app/shared/sidebar/filter/sidebar-filter.component.html new file mode 100644 index 0000000000..b0209d9900 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.component.html @@ -0,0 +1,24 @@ +
+
+
+ {{ label | translate }} +
+ + +
+ +
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.scss b/src/app/shared/sidebar/filter/sidebar-filter.component.scss new file mode 100644 index 0000000000..68949f3450 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.component.scss @@ -0,0 +1,12 @@ +:host .facet-filter { + border: 1px solid map-get($theme-colors, light); + cursor: pointer; + + .sidebar-filter-wrapper.closed { + overflow: hidden; + } + + .filter-toggle { + line-height: $line-height-base; + } +} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.ts b/src/app/shared/sidebar/filter/sidebar-filter.component.ts new file mode 100644 index 0000000000..4d5f41d971 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.component.ts @@ -0,0 +1,84 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Observable } from 'rxjs'; +import { SidebarFilterService } from './sidebar-filter.service'; +import { slide } from '../../animations/slide'; + +@Component({ + selector: 'ds-sidebar-filter', + styleUrls: ['./sidebar-filter.component.scss'], + templateUrl: './sidebar-filter.component.html', + animations: [slide], +}) +export class SidebarFilterComponent implements OnInit { + + @Input() name:string; + @Input() type:string; + @Input() label:string; + @Input() expanded = true; + @Input() selectedValues:Observable; + @Output() submitValue:EventEmitter = new EventEmitter(); + @Output() removeValue:EventEmitter = new EventEmitter(); + + /** + * True when the filter is 100% collapsed in the UI + */ + closed = true; + + /** + * Emits true when the filter is currently collapsed in the store + */ + collapsed$:Observable; + + constructor( + protected filterService:SidebarFilterService + ) { + } + + /** + * Changes the state for this filter to collapsed when it's expanded and to expanded it when it's collapsed + */ + toggle() { + this.filterService.toggle(this.name); + } + + /** + * 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 === 'collapsed') { + this.closed = false; + } + } + + /** + * Method to change this.collapsed to true when the slide animation starts and is sliding closed + * @param event The animation event + */ + startSlide(event:any):void { + if (event.toState === 'collapsed') { + this.closed = true; + } + } + + ngOnInit():void { + this.initializeFilter(); + this.collapsed$ = this.isCollapsed(); + } + + /** + * Sets the initial state of the filter + */ + initializeFilter() { + this.filterService.initializeFilter(this.name, this.expanded); + } + + /** + * Checks if the filter is currently collapsed + * @returns {Observable} Emits true when the current state of the filter is collapsed, false when it's expanded + */ + private isCollapsed():Observable { + return this.filterService.isCollapsed(this.name); + } + +} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts b/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts new file mode 100644 index 0000000000..d25737eaa9 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts @@ -0,0 +1,70 @@ +import { + FilterInitializeAction, + SidebarFilterAction, + SidebarFilterActionTypes +} from './sidebar-filter.actions'; + +/** + * Interface that represents the state for a single filters + */ +export interface SidebarFilterState { + filterCollapsed:boolean, +} + +/** + * Interface that represents the state for all available filters + */ +export interface SidebarFiltersState { + [name:string]:SidebarFilterState +} + +const initialState:SidebarFiltersState = Object.create(null); + +/** + * Performs a filter action on the current state + * @param {SidebarFiltersState} state The state before the action is performed + * @param {SidebarFilterAction} action The action that should be performed + * @returns {SidebarFiltersState} The state after the action is performed + */ +export function sidebarFilterReducer(state = initialState, action:SidebarFilterAction):SidebarFiltersState { + + switch (action.type) { + + case SidebarFilterActionTypes.INITIALIZE: { + const initAction = (action as FilterInitializeAction); + return Object.assign({}, state, { + [action.filterName]: { + filterCollapsed: !initAction.initiallyExpanded, + } + }); + } + + case SidebarFilterActionTypes.COLLAPSE: { + return Object.assign({}, state, { + [action.filterName]: { + filterCollapsed: true, + } + }); + } + + case SidebarFilterActionTypes.EXPAND: { + return Object.assign({}, state, { + [action.filterName]: { + filterCollapsed: false, + } + }); + } + + case SidebarFilterActionTypes.TOGGLE: { + return Object.assign({}, state, { + [action.filterName]: { + filterCollapsed: !state[action.filterName].filterCollapsed, + } + }); + } + + default: { + return state; + } + } +} diff --git a/src/app/shared/sidebar/filter/sidebar-filter.service.ts b/src/app/shared/sidebar/filter/sidebar-filter.service.ts new file mode 100644 index 0000000000..b08c7a8b73 --- /dev/null +++ b/src/app/shared/sidebar/filter/sidebar-filter.service.ts @@ -0,0 +1,87 @@ +import { Injectable } from '@angular/core'; +import { + FilterCollapseAction, + FilterExpandAction, FilterInitializeAction, + FilterToggleAction +} from './sidebar-filter.actions'; +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; +import { SidebarFiltersState, SidebarFilterState } from './sidebar-filter.reducer'; +import { Observable } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { hasValue } from '../../empty.util'; + +@Injectable() +export class SidebarFilterService { + + constructor(private store:Store) { + } + + /** + * Dispatches an initialize action to the store for a given filter + * @param {string} filter The filter for which the action is dispatched + * @param {boolean} expanded If the filter should be open from the start + */ + public initializeFilter(filter:string, expanded:boolean):void { + this.store.dispatch(new FilterInitializeAction(filter, expanded)); + } + + /** + * Dispatches a collapse action to the store for a given filter + * @param {string} filterName The filter for which the action is dispatched + */ + public collapse(filterName:string):void { + this.store.dispatch(new FilterCollapseAction(filterName)); + } + + /** + * Dispatches an expand action to the store for a given filter + * @param {string} filterName The filter for which the action is dispatched + */ + public expand(filterName:string):void { + this.store.dispatch(new FilterExpandAction(filterName)); + } + + /** + * Dispatches a toggle action to the store for a given filter + * @param {string} filterName The filter for which the action is dispatched + */ + public toggle(filterName:string):void { + this.store.dispatch(new FilterToggleAction(filterName)); + } + + /** + * Checks if the state of a given filter is currently collapsed or not + * @param {string} filterName The filtername for which the collapsed state is checked + * @returns {Observable} Emits the current collapsed state of the given filter, if it's unavailable, return false + */ + isCollapsed(filterName:string):Observable { + return this.store.pipe( + select(filterByNameSelector(filterName)), + map((object:SidebarFilterState) => { + if (object) { + return object.filterCollapsed; + } else { + return false; + } + }), + distinctUntilChanged() + ); + } + +} + +const filterStateSelector = (state:SidebarFiltersState) => state.sidebarFilter; + +function filterByNameSelector(name:string):MemoizedSelector { + return keySelector(name); +} + +export function keySelector(key:string):MemoizedSelector { + return createSelector(filterStateSelector, (state:SidebarFilterState) => { + if (hasValue(state)) { + return state[key]; + } else { + return undefined; + } + }); +} diff --git a/src/app/shared/sidebar/page-with-sidebar.component.spec.ts b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts index dfe035d2be..77f59090ab 100644 --- a/src/app/shared/sidebar/page-with-sidebar.component.spec.ts +++ b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts @@ -2,8 +2,8 @@ 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 { SidebarService } from './sidebar.service'; +import { HostWindowService } from '../host-window.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; describe('PageWithSidebarComponent', () => { diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.html b/src/app/shared/sidebar/sidebar-dropdown.component.html new file mode 100644 index 0000000000..0c2a1c05d2 --- /dev/null +++ b/src/app/shared/sidebar/sidebar-dropdown.component.html @@ -0,0 +1,6 @@ +
+
+ +
diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.scss b/src/app/shared/sidebar/sidebar-dropdown.component.scss new file mode 100644 index 0000000000..1c025095dd --- /dev/null +++ b/src/app/shared/sidebar/sidebar-dropdown.component.scss @@ -0,0 +1,3 @@ +.setting-option { + border: 1px solid map-get($theme-colors, light); +} diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.ts b/src/app/shared/sidebar/sidebar-dropdown.component.ts new file mode 100644 index 0000000000..d4c996157c --- /dev/null +++ b/src/app/shared/sidebar/sidebar-dropdown.component.ts @@ -0,0 +1,12 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'ds-sidebar-dropdown', + styleUrls: ['./sidebar-dropdown.component.scss'], + templateUrl: './sidebar-dropdown.component.html', +}) +export class SidebarDropdownComponent { + @Input() id:string; + @Input() label:string; + @Output() change:EventEmitter = new EventEmitter(); +} diff --git a/src/app/shared/sidebar/sidebar.reducer.ts b/src/app/shared/sidebar/sidebar.reducer.ts index 3c3dc63b0d..05e7d38d48 100644 --- a/src/app/shared/sidebar/sidebar.reducer.ts +++ b/src/app/shared/sidebar/sidebar.reducer.ts @@ -12,7 +12,7 @@ const initialState: SidebarState = { }; /** - * Performs a search sidebar action on the current state + * Performs a 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 diff --git a/src/app/shared/sidebar/sidebar.service.ts b/src/app/shared/sidebar/sidebar.service.ts index 2e3303887b..125fb5b629 100644 --- a/src/app/shared/sidebar/sidebar.service.ts +++ b/src/app/shared/sidebar/sidebar.service.ts @@ -7,7 +7,7 @@ import { AppState } from '../../app.reducer'; import { HostWindowService } from '../host-window.service'; import { map } from 'rxjs/operators'; -const sidebarStateSelector = (state: AppState) => state.searchSidebar; +const sidebarStateSelector = (state: AppState) => state.sidebar; const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SidebarState) => sidebar.sidebarCollapsed); /** From 78a29a87709c55aedba7d89a11104497eaab236c Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Thu, 14 Nov 2019 14:05:52 +0100 Subject: [PATCH 3/5] Replace double quotes by single quotes --- src/app/+search-page/search-page.module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 8d583a78af..678991e8b1 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -34,7 +34,7 @@ import { SearchLabelComponent } from './search-labels/search-label/search-label. import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; import { FilteredSearchPageComponent } from './filtered-search-page.component'; -import { SidebarFilterService } from "../shared/sidebar/filter/sidebar-filter.service"; +import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service'; const effects = [ SidebarEffects From 56d6965233eda245de4ce76920d95a9c00c5c51e Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Fri, 15 Nov 2019 12:00:55 +0100 Subject: [PATCH 4/5] Update the search-settings component specs --- .../search-settings.component.spec.ts | 132 ++++++++++-------- 1 file changed, 72 insertions(+), 60 deletions(-) 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 729c228f44..e0fbc84deb 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 @@ -12,79 +12,87 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe'; import { By } from '@angular/platform-browser'; import { SearchFilterService } from '../search-filters/search-filter/search-filter.service'; -import { hot } from 'jasmine-marbles'; import { VarDirective } from '../../shared/utils/var.directive'; -import { first } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component'; describe('SearchSettingsComponent', () => { - let comp: SearchSettingsComponent; - let fixture: ComponentFixture; - let searchServiceObject: SearchService; + let comp:SearchSettingsComponent; + let fixture:ComponentFixture; + let searchServiceObject:SearchService; - const pagination: PaginationComponentOptions = new PaginationComponentOptions(); - pagination.id = 'search-results-pagination'; - pagination.currentPage = 1; - pagination.pageSize = 10; - const sort: SortOptions = new SortOptions('score', SortDirection.DESC); - const mockResults = ['test', 'data']; - const searchServiceStub = { - searchOptions: { pagination: pagination, sort: sort }, - search: () => mockResults - }; + let pagination:PaginationComponentOptions; + let sort:SortOptions; + let mockResults; + let searchServiceStub; - const queryParam = 'test query'; - const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; - const paginatedSearchOptions = { - query: queryParam, - scope: scopeParam, - pagination, - sort - }; + let queryParam; + let scopeParam; + let paginatedSearchOptions; - const activatedRouteStub = { - queryParams: observableOf({ - query: queryParam, - scope: scopeParam - }) - }; + let activatedRouteStub; - const sidebarService = { - isCollapsed: observableOf(true), - collapse: () => this.isCollapsed = observableOf(true), - expand: () => this.isCollapsed = observableOf(false) - }; + let sidebarService; beforeEach(async(() => { + pagination = new PaginationComponentOptions(); + pagination.id = 'search-results-pagination'; + pagination.currentPage = 1; + pagination.pageSize = 10; + sort = new SortOptions('score', SortDirection.DESC); + mockResults = ['test', 'data']; + searchServiceStub = { + searchOptions: {pagination: pagination, sort: sort}, + search: () => mockResults, + }; + + queryParam = 'test query'; + scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; + paginatedSearchOptions = { + query: queryParam, + scope: scopeParam, + pagination, + sort, + }; + + activatedRouteStub = { + queryParams: observableOf({ + query: queryParam, + scope: scopeParam, + }), + }; + + sidebarService = { + isCollapsed: observableOf(true), + collapse: () => this.isCollapsed = observableOf(true), + expand: () => this.isCollapsed = observableOf(false), + }; + TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])], declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective], providers: [ - { provide: SearchService, useValue: searchServiceStub }, + {provide: SearchService, useValue: searchServiceStub}, - { provide: ActivatedRoute, useValue: activatedRouteStub }, + {provide: ActivatedRoute, useValue: activatedRouteStub}, { provide: SidebarService, - useValue: sidebarService + useValue: sidebarService, }, { provide: SearchFilterService, - useValue: {} + useValue: {}, }, { provide: SEARCH_CONFIG_SERVICE, useValue: { - paginatedSearchOptions: hot('a', { - a: paginatedSearchOptions - }), - getCurrentScope: hot('a', { - a: 'test-id' - }), - } + paginatedSearchOptions: observableOf(paginatedSearchOptions), + getCurrentScope: observableOf('test-id'), + }, }, ], - schemas: [NO_ERRORS_SCHEMA] + schemas: [NO_ERRORS_SCHEMA], }).compileComponents(); })); @@ -101,42 +109,46 @@ describe('SearchSettingsComponent', () => { }); - it('it should show the order settings with the respective selectable options', () => { - (comp as any).searchOptions$.pipe(first()).subscribe((options) => { + it('it should show the order settings with the respective selectable options', (done) => { + (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { fixture.detectChanges(); const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); expect(orderSetting).toBeDefined(); - const childElements = orderSetting.query(By.css('.form-control')).children; + const childElements = orderSetting.queryAll(By.css('option')); expect(childElements.length).toEqual(comp.searchOptionPossibilities.length); + done(); }); }); - it('it should show the size settings with the respective selectable options', () => { - (comp as any).searchOptions$.pipe(first()).subscribe((options) => { + it('it should show the size settings with the respective selectable options', (done) => { + (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { fixture.detectChanges(); const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings')); expect(pageSizeSetting).toBeDefined(); - const childElements = pageSizeSetting.query(By.css('.form-control')).children; + const childElements = pageSizeSetting.queryAll(By.css('option')); expect(childElements.length).toEqual(options.pagination.pageSizeOptions.length); - } - ) + done(); + }, + ); }); - it('should have the proper order value selected by default', () => { - (comp as any).searchOptions$.pipe(first()).subscribe((options) => { + it('should have the proper order value selected by default', (done) => { + (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { fixture.detectChanges(); const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings')); - const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]')); + const childElementToBeSelected = orderSetting.query(By.css('option[value="0"][selected="selected"]')); expect(childElementToBeSelected).toBeDefined(); + done(); }); }); - it('should have the proper rpp value selected by default', () => { - (comp as any).searchOptions$.pipe(first()).subscribe((options) => { + it('should have the proper rpp value selected by default', (done) => { + (comp as any).searchOptions$.pipe(take(1)).subscribe((options) => { fixture.detectChanges(); const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings')); - const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]')); + const childElementToBeSelected = pageSizeSetting.query(By.css('option[value="10"][selected="selected"]')); expect(childElementToBeSelected).toBeDefined(); + done(); }); }); From c83c861e8528caafdd8f3c649d9cb5dfe6cabad4 Mon Sep 17 00:00:00 2001 From: Antoine Snyers Date: Wed, 27 Nov 2019 14:11:43 +0100 Subject: [PATCH 5/5] Update TypeDocs --- .../sidebar/filter/sidebar-filter.component.html | 12 +++++++----- .../sidebar/filter/sidebar-filter.component.ts | 9 +++++++-- .../shared/sidebar/filter/sidebar-filter.service.ts | 3 +++ .../shared/sidebar/page-with-sidebar.component.ts | 8 +++++++- src/app/shared/sidebar/sidebar-dropdown.component.ts | 4 ++++ src/app/shared/sidebar/sidebar.service.ts | 2 +- 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.html b/src/app/shared/sidebar/filter/sidebar-filter.component.html index b0209d9900..bd392aa715 100644 --- a/src/app/shared/sidebar/filter/sidebar-filter.component.html +++ b/src/app/shared/sidebar/filter/sidebar-filter.component.html @@ -12,11 +12,13 @@ class="sidebar-filter-wrapper" [ngClass]="{'closed' : closed}">
- - + + + +
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.ts b/src/app/shared/sidebar/filter/sidebar-filter.component.ts index 4d5f41d971..2a98565639 100644 --- a/src/app/shared/sidebar/filter/sidebar-filter.component.ts +++ b/src/app/shared/sidebar/filter/sidebar-filter.component.ts @@ -9,14 +9,18 @@ import { slide } from '../../animations/slide'; templateUrl: './sidebar-filter.component.html', animations: [slide], }) +/** + * This components renders a sidebar filter including the label and the selected values. + * The filter input itself should still be provided in the content. + */ export class SidebarFilterComponent implements OnInit { @Input() name:string; @Input() type:string; @Input() label:string; @Input() expanded = true; + @Input() singleValue = false; @Input() selectedValues:Observable; - @Output() submitValue:EventEmitter = new EventEmitter(); @Output() removeValue:EventEmitter = new EventEmitter(); /** @@ -30,7 +34,7 @@ export class SidebarFilterComponent implements OnInit { collapsed$:Observable; constructor( - protected filterService:SidebarFilterService + protected filterService:SidebarFilterService, ) { } @@ -62,6 +66,7 @@ export class SidebarFilterComponent implements OnInit { } ngOnInit():void { + this.closed = !this.expanded; this.initializeFilter(); this.collapsed$ = this.isCollapsed(); } diff --git a/src/app/shared/sidebar/filter/sidebar-filter.service.ts b/src/app/shared/sidebar/filter/sidebar-filter.service.ts index b08c7a8b73..2ff28fd2f5 100644 --- a/src/app/shared/sidebar/filter/sidebar-filter.service.ts +++ b/src/app/shared/sidebar/filter/sidebar-filter.service.ts @@ -10,6 +10,9 @@ import { Observable } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { hasValue } from '../../empty.util'; +/** + * Service that performs all actions that have to do with sidebar filters like collapsing or expanding them. + */ @Injectable() export class SidebarFilterService { diff --git a/src/app/shared/sidebar/page-with-sidebar.component.ts b/src/app/shared/sidebar/page-with-sidebar.component.ts index 8020d492ec..8b7f987a37 100644 --- a/src/app/shared/sidebar/page-with-sidebar.component.ts +++ b/src/app/shared/sidebar/page-with-sidebar.component.ts @@ -1,7 +1,7 @@ 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 { Observable } from 'rxjs'; import { pushInOut } from '../animations/push'; import { map } from 'rxjs/operators'; @@ -11,6 +11,12 @@ import { map } from 'rxjs/operators'; templateUrl: './page-with-sidebar.component.html', animations: [pushInOut], }) +/** + * This component takes care of displaying the sidebar properly on all viewports. It does not + * provide default buttons to open or close the sidebar. Instead the parent component is expected + * to provide the content of the sidebar through an input. The main content of the page goes in + * the template outlet (inside the page-width-sidebar tags). + */ export class PageWithSidebarComponent implements OnInit { @Input() id:string; @Input() sidebarContent:TemplateRef; diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.ts b/src/app/shared/sidebar/sidebar-dropdown.component.ts index d4c996157c..313538eded 100644 --- a/src/app/shared/sidebar/sidebar-dropdown.component.ts +++ b/src/app/shared/sidebar/sidebar-dropdown.component.ts @@ -5,6 +5,10 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; styleUrls: ['./sidebar-dropdown.component.scss'], templateUrl: './sidebar-dropdown.component.html', }) +/** + * This components renders a sidebar dropdown including the label. + * The options should still be provided in the content. + */ export class SidebarDropdownComponent { @Input() id:string; @Input() label:string; diff --git a/src/app/shared/sidebar/sidebar.service.ts b/src/app/shared/sidebar/sidebar.service.ts index 125fb5b629..8e3bbbf117 100644 --- a/src/app/shared/sidebar/sidebar.service.ts +++ b/src/app/shared/sidebar/sidebar.service.ts @@ -11,7 +11,7 @@ const sidebarStateSelector = (state: AppState) => state.sidebar; const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SidebarState) => sidebar.sidebarCollapsed); /** - * Service that performs all actions that have to do with the search sidebar + * Service that performs all actions that have to do with the sidebar */ @Injectable() export class SidebarService {