mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge pull request #513 from atmire/sidebar-refactoring
Sidebar refactoring
This commit is contained in:
@@ -22,7 +22,7 @@ import { SearchConfigurationServiceStub } from '../shared/testing/search-configu
|
|||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
import { SearchService } from '../+search-page/search-service/search.service';
|
||||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
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 { SearchFilterService } from '../+search-page/search-filters/search-filter/search-filter.service';
|
||||||
import { RoleDirective } from '../shared/roles/role.directive';
|
import { RoleDirective } from '../shared/roles/role.directive';
|
||||||
import { RoleService } from '../core/roles/role.service';
|
import { RoleService } from '../core/roles/role.service';
|
||||||
@@ -109,7 +109,7 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchSidebarService,
|
provide: SidebarService,
|
||||||
useValue: sidebarService
|
useValue: sidebarService
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -17,7 +17,7 @@ import { pushInOut } from '../shared/animations/push';
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
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 { hasValue } from '../shared/empty.util';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
||||||
@@ -102,7 +102,7 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
context$: Observable<Context>;
|
context$: Observable<Context>;
|
||||||
|
|
||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private sidebarService: SearchSidebarService,
|
private sidebarService: SidebarService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { SearchService } from './search-service/search.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 { SearchPageComponent } from './search-page.component';
|
||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
import { pushInOut } from '../shared/animations/push';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
@@ -36,7 +36,7 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem
|
|||||||
@Input() configuration: string;
|
@Input() configuration: string;
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SearchSidebarService,
|
protected sidebarService: SidebarService,
|
||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService) {
|
protected routeService: RouteService) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { SearchService } from './search-service/search.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 { SearchPageComponent } from './search-page.component';
|
||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
import { pushInOut } from '../shared/animations/push';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
@@ -38,7 +38,7 @@ export class FilteredSearchPageComponent extends SearchPageComponent implements
|
|||||||
@Input() fixedFilterQuery: string;
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SearchSidebarService,
|
protected sidebarService: SidebarService,
|
||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService) {
|
protected routeService: RouteService) {
|
||||||
|
@@ -1,7 +1,18 @@
|
|||||||
<div class="facet-filter d-block mb-3 p-3" *ngIf="active$ | async">
|
<div class="facet-filter d-block mb-3 p-3" *ngIf="active$ | async">
|
||||||
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fas float-right"
|
<div (click)="toggle()" class="filter-name">
|
||||||
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
<h5 class="d-inline-block mb-0">
|
||||||
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{'closed' : closed}">
|
{{'search.filters.filter.' + filter.name + '.head'| translate}}
|
||||||
<ds-search-facet-filter-wrapper [filterConfig]="filter" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-filter-wrapper>
|
</h5>
|
||||||
|
<span class="filter-toggle fas float-right"
|
||||||
|
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'"
|
||||||
|
(@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)"
|
||||||
|
class="search-filter-wrapper" [ngClass]="{'closed' : closed}">
|
||||||
|
<ds-search-facet-filter-wrapper
|
||||||
|
[filterConfig]="filter"
|
||||||
|
[inPlaceSearch]="inPlaceSearch">
|
||||||
|
</ds-search-facet-filter-wrapper>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -138,7 +138,7 @@ describe('SearchFilterService', () => {
|
|||||||
service.expand(mockFilterConfig.name);
|
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));
|
expect(store.dispatch).toHaveBeenCalledWith(new SearchFilterExpandAction(mockFilterConfig.name));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,27 +1,15 @@
|
|||||||
<div class="container">
|
<div class="container" *ngIf="(isXsOrSm$ | async)">
|
||||||
<div class="search-page row">
|
|
||||||
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-{{sideBarWidth}} sidebar-md-sticky"
|
|
||||||
id="search-sidebar"
|
|
||||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements" [inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
|
||||||
<div class="col-12 col-md-{{12 - sideBarWidth}}">
|
|
||||||
<ds-search-form *ngIf="searchEnabled" id="search-form"
|
|
||||||
[query]="(searchOptions$ | async)?.query"
|
|
||||||
[scope]="(searchOptions$ | async)?.scope"
|
|
||||||
[currentUrl]="searchLink"
|
|
||||||
[scopes]="(scopeListRD$ | async)"
|
|
||||||
[inPlaceSearch]="inPlaceSearch">
|
|
||||||
</ds-search-form>
|
|
||||||
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="search-body"
|
<div class="col-12">
|
||||||
class="row-offcanvas row-offcanvas-left"
|
<ng-template *ngTemplateOutlet="searchForm"></ng-template>
|
||||||
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
</div>
|
||||||
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
</div>
|
||||||
id="search-sidebar-sm"
|
</div>
|
||||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
<ds-page-with-sidebar [id]="'search-page'" [sidebarContent]="sidebarContent">
|
||||||
(toggleSidebar)="closeSidebar()"
|
<div class="row">
|
||||||
[ngClass]="{'active': !(isSidebarCollapsed$ | async)}">
|
<div class="col-12" *ngIf="!(isXsOrSm$ | async)">
|
||||||
</ds-search-sidebar>
|
<ng-template *ngTemplateOutlet="searchForm"></ng-template>
|
||||||
|
</div>
|
||||||
<div id="search-content" class="col-12">
|
<div id="search-content" class="col-12">
|
||||||
<div class="d-block d-md-none search-controls clearfix">
|
<div class="d-block d-md-none search-controls clearfix">
|
||||||
<ds-view-mode-switch [inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
<ds-view-mode-switch [inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
||||||
@@ -37,7 +25,26 @@
|
|||||||
[disableHeader]="!searchEnabled"></ds-search-results>
|
[disableHeader]="!searchEnabled"></ds-search-results>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ds-page-with-sidebar>
|
||||||
</div>
|
|
||||||
</div>
|
<ng-template #sidebarContent>
|
||||||
</div>
|
<ds-search-sidebar id="search-sidebar" *ngIf="!(isXsOrSm$ | async)"
|
||||||
|
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||||
|
[inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
||||||
|
<ds-search-sidebar id="search-sidebar-sm" *ngIf="(isXsOrSm$ | async)"
|
||||||
|
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||||
|
(toggleSidebar)="closeSidebar()"
|
||||||
|
>
|
||||||
|
</ds-search-sidebar>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #searchForm>
|
||||||
|
<ds-search-form *ngIf="searchEnabled" id="search-form"
|
||||||
|
[query]="(searchOptions$ | async)?.query"
|
||||||
|
[scope]="(searchOptions$ | async)?.scope"
|
||||||
|
[currentUrl]="searchLink"
|
||||||
|
[scopes]="(scopeListRD$ | async)"
|
||||||
|
[inPlaceSearch]="inPlaceSearch">
|
||||||
|
</ds-search-form>
|
||||||
|
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
|
||||||
|
</ng-template>
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
@include media-breakpoint-down(md) {
|
@include media-breakpoint-down(md) {
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -9,44 +8,3 @@
|
|||||||
/deep/ .search-controls {
|
/deep/ .search-controls {
|
||||||
margin-bottom: $spacer;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
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 { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
@@ -115,7 +115,7 @@ export function configureSearchComponentTestingModule(compType) {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchSidebarService,
|
provide: SidebarService,
|
||||||
useValue: 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');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -9,7 +9,7 @@ import { HostWindowService } from '../shared/host-window.service';
|
|||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
import { SearchResult } from './search-result.model';
|
import { SearchResult } from './search-result.model';
|
||||||
import { SearchService } from './search-service/search.service';
|
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 { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
@@ -102,7 +102,7 @@ export class SearchPageComponent implements OnInit {
|
|||||||
isSidebarCollapsed$: Observable<boolean>;
|
isSidebarCollapsed$: Observable<boolean>;
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SearchSidebarService,
|
protected sidebarService: SidebarService,
|
||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService) {
|
protected routeService: RouteService) {
|
||||||
|
@@ -8,8 +8,8 @@ import { SearchResultsComponent } from './search-results/search-results.componen
|
|||||||
import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'
|
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 { 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 { SearchSidebarComponent } from './search-sidebar/search-sidebar.component';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects';
|
import { SidebarEffects } from '../shared/sidebar/sidebar-effects.service';
|
||||||
import { SearchSettingsComponent } from './search-settings/search-settings.component';
|
import { SearchSettingsComponent } from './search-settings/search-settings.component';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { SearchFiltersComponent } from './search-filters/search-filters.component';
|
import { SearchFiltersComponent } from './search-filters/search-filters.component';
|
||||||
@@ -33,9 +33,10 @@ import { SearchLabelComponent } from './search-labels/search-label/search-label.
|
|||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
||||||
|
import { SidebarFilterService } from '../shared/sidebar/filter/sidebar-filter.service';
|
||||||
|
|
||||||
const effects = [
|
const effects = [
|
||||||
SearchSidebarEffects
|
SidebarEffects
|
||||||
];
|
];
|
||||||
|
|
||||||
const components = [
|
const components = [
|
||||||
@@ -69,11 +70,12 @@ const components = [
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EffectsModule.forFeature(effects),
|
EffectsModule.forFeature(effects),
|
||||||
CoreModule.forRoot()
|
CoreModule.forRoot(),
|
||||||
],
|
],
|
||||||
declarations: components,
|
declarations: components,
|
||||||
providers: [
|
providers: [
|
||||||
SearchSidebarService,
|
SidebarService,
|
||||||
|
SidebarFilterService,
|
||||||
SearchFilterService,
|
SearchFilterService,
|
||||||
SearchFixedFilterService,
|
SearchFixedFilterService,
|
||||||
ConfigurationSearchPageGuard,
|
ConfigurationSearchPageGuard,
|
||||||
|
@@ -1,24 +1,32 @@
|
|||||||
<ng-container *ngVar="(searchOptions$ | async) as config">
|
<ng-container *ngVar="(searchOptions$ | async) as config">
|
||||||
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
|
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
|
||||||
<div *ngIf="config?.sort" class="setting-option result-order-settings mb-3 p-3">
|
|
||||||
<h5>{{ 'search.sidebar.settings.sort-by' | translate}}</h5>
|
<div class="result-order-settings">
|
||||||
<select class="form-control" (change)="reloadOrder($event)">
|
<ds-sidebar-dropdown
|
||||||
|
*ngIf="config?.sort"
|
||||||
|
[id]="'search-sidebar-sort'"
|
||||||
|
[label]="'search.sidebar.settings.sort-by'"
|
||||||
|
(change)="reloadOrder($event)"
|
||||||
|
>
|
||||||
<option *ngFor="let sortOption of searchOptionPossibilities"
|
<option *ngFor="let sortOption of searchOptionPossibilities"
|
||||||
[value]="sortOption.field + ',' + sortOption.direction.toString()"
|
[value]="sortOption.field + ',' + sortOption.direction.toString()"
|
||||||
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
|
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
|
||||||
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
|
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</ds-sidebar-dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-option page-size-settings mb-3 p-3">
|
<div class="page-size-settings">
|
||||||
<h5>{{ 'search.sidebar.settings.rpp' | translate}}</h5>
|
<ds-sidebar-dropdown
|
||||||
<select class="form-control" (change)="reloadRPP($event)">
|
[id]="'search-sidebar-rpp'"
|
||||||
|
[label]="'search.sidebar.settings.rpp'"
|
||||||
|
(change)="reloadRPP($event)"
|
||||||
|
>
|
||||||
<option *ngFor="let pageSizeOption of config?.pagination.pageSizeOptions"
|
<option *ngFor="let pageSizeOption of config?.pagination.pageSizeOptions"
|
||||||
[value]="pageSizeOption"
|
[value]="pageSizeOption"
|
||||||
[selected]="pageSizeOption === +config?.pagination.pageSize ? 'selected': null">
|
[selected]="pageSizeOption === +config?.pagination.pageSize ? 'selected': null">
|
||||||
{{pageSizeOption}}
|
{{pageSizeOption}}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</ds-sidebar-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
@@ -7,14 +7,13 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
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 { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
||||||
import { hot } from 'jasmine-marbles';
|
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
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';
|
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
|
||||||
|
|
||||||
describe('SearchSettingsComponent', () => {
|
describe('SearchSettingsComponent', () => {
|
||||||
@@ -23,40 +22,53 @@ describe('SearchSettingsComponent', () => {
|
|||||||
let fixture:ComponentFixture<SearchSettingsComponent>;
|
let fixture:ComponentFixture<SearchSettingsComponent>;
|
||||||
let searchServiceObject:SearchService;
|
let searchServiceObject:SearchService;
|
||||||
|
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
let pagination:PaginationComponentOptions;
|
||||||
|
let sort:SortOptions;
|
||||||
|
let mockResults;
|
||||||
|
let searchServiceStub;
|
||||||
|
|
||||||
|
let queryParam;
|
||||||
|
let scopeParam;
|
||||||
|
let paginatedSearchOptions;
|
||||||
|
|
||||||
|
let activatedRouteStub;
|
||||||
|
|
||||||
|
let sidebarService;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
pagination = new PaginationComponentOptions();
|
||||||
pagination.id = 'search-results-pagination';
|
pagination.id = 'search-results-pagination';
|
||||||
pagination.currentPage = 1;
|
pagination.currentPage = 1;
|
||||||
pagination.pageSize = 10;
|
pagination.pageSize = 10;
|
||||||
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
sort = new SortOptions('score', SortDirection.DESC);
|
||||||
const mockResults = ['test', 'data'];
|
mockResults = ['test', 'data'];
|
||||||
const searchServiceStub = {
|
searchServiceStub = {
|
||||||
searchOptions: {pagination: pagination, sort: sort},
|
searchOptions: {pagination: pagination, sort: sort},
|
||||||
search: () => mockResults
|
search: () => mockResults,
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryParam = 'test query';
|
queryParam = 'test query';
|
||||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||||
const paginatedSearchOptions = {
|
paginatedSearchOptions = {
|
||||||
query: queryParam,
|
query: queryParam,
|
||||||
scope: scopeParam,
|
scope: scopeParam,
|
||||||
pagination,
|
pagination,
|
||||||
sort
|
sort,
|
||||||
};
|
};
|
||||||
|
|
||||||
const activatedRouteStub = {
|
activatedRouteStub = {
|
||||||
queryParams: observableOf({
|
queryParams: observableOf({
|
||||||
query: queryParam,
|
query: queryParam,
|
||||||
scope: scopeParam
|
scope: scopeParam,
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarService = {
|
sidebarService = {
|
||||||
isCollapsed: observableOf(true),
|
isCollapsed: observableOf(true),
|
||||||
collapse: () => this.isCollapsed = observableOf(true),
|
collapse: () => this.isCollapsed = observableOf(true),
|
||||||
expand: () => this.isCollapsed = observableOf(false)
|
expand: () => this.isCollapsed = observableOf(false),
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||||
declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective],
|
declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective],
|
||||||
@@ -65,26 +77,22 @@ describe('SearchSettingsComponent', () => {
|
|||||||
|
|
||||||
{provide: ActivatedRoute, useValue: activatedRouteStub},
|
{provide: ActivatedRoute, useValue: activatedRouteStub},
|
||||||
{
|
{
|
||||||
provide: SearchSidebarService,
|
provide: SidebarService,
|
||||||
useValue: sidebarService
|
useValue: sidebarService,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchFilterService,
|
provide: SearchFilterService,
|
||||||
useValue: {}
|
useValue: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
provide: SEARCH_CONFIG_SERVICE,
|
||||||
useValue: {
|
useValue: {
|
||||||
paginatedSearchOptions: hot('a', {
|
paginatedSearchOptions: observableOf(paginatedSearchOptions),
|
||||||
a: paginatedSearchOptions
|
getCurrentScope: observableOf('test-id'),
|
||||||
}),
|
},
|
||||||
getCurrentScope: hot('a', {
|
|
||||||
a: 'test-id'
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -101,42 +109,46 @@ describe('SearchSettingsComponent', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should show the order settings with the respective selectable options', () => {
|
it('it should show the order settings with the respective selectable options', (done) => {
|
||||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||||
expect(orderSetting).toBeDefined();
|
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);
|
expect(childElements.length).toEqual(comp.searchOptionPossibilities.length);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should show the size settings with the respective selectable options', () => {
|
it('it should show the size settings with the respective selectable options', (done) => {
|
||||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
||||||
expect(pageSizeSetting).toBeDefined();
|
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);
|
expect(childElements.length).toEqual(options.pagination.pageSizeOptions.length);
|
||||||
}
|
done();
|
||||||
)
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the proper order value selected by default', () => {
|
it('should have the proper order value selected by default', (done) => {
|
||||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
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();
|
expect(childElementToBeSelected).toBeDefined();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have the proper rpp value selected by default', () => {
|
it('should have the proper rpp value selected by default', (done) => {
|
||||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
(comp as any).searchOptions$.pipe(take(1)).subscribe((options) => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
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();
|
expect(childElementToBeSelected).toBeDefined();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,11 +1,15 @@
|
|||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
import { ActionReducerMap, createSelector, MemoizedSelector, State } from '@ngrx/store';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||||
import {
|
import {
|
||||||
SearchSidebarState,
|
SidebarState,
|
||||||
sidebarReducer
|
sidebarReducer
|
||||||
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
} from './shared/sidebar/sidebar.reducer';
|
||||||
|
import {
|
||||||
|
SidebarFilterState,
|
||||||
|
sidebarFilterReducer, SidebarFiltersState
|
||||||
|
} from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||||
import {
|
import {
|
||||||
filterReducer,
|
filterReducer,
|
||||||
SearchFiltersState
|
SearchFiltersState
|
||||||
@@ -37,7 +41,8 @@ export interface AppState {
|
|||||||
metadataRegistry: MetadataRegistryState;
|
metadataRegistry: MetadataRegistryState;
|
||||||
bitstreamFormats: BitstreamFormatRegistryState;
|
bitstreamFormats: BitstreamFormatRegistryState;
|
||||||
notifications: NotificationsState;
|
notifications: NotificationsState;
|
||||||
searchSidebar: SearchSidebarState;
|
sidebar: SidebarState;
|
||||||
|
sidebarFilter: SidebarFiltersState;
|
||||||
searchFilter: SearchFiltersState;
|
searchFilter: SearchFiltersState;
|
||||||
truncatable: TruncatablesState;
|
truncatable: TruncatablesState;
|
||||||
cssVariables: CSSVariablesState;
|
cssVariables: CSSVariablesState;
|
||||||
@@ -53,7 +58,8 @@ export const appReducers: ActionReducerMap<AppState> = {
|
|||||||
metadataRegistry: metadataRegistryReducer,
|
metadataRegistry: metadataRegistryReducer,
|
||||||
bitstreamFormats: bitstreamFormatReducer,
|
bitstreamFormats: bitstreamFormatReducer,
|
||||||
notifications: notificationsReducer,
|
notifications: notificationsReducer,
|
||||||
searchSidebar: sidebarReducer,
|
sidebar: sidebarReducer,
|
||||||
|
sidebarFilter: sidebarFilterReducer,
|
||||||
searchFilter: filterReducer,
|
searchFilter: filterReducer,
|
||||||
truncatable: truncatableReducer,
|
truncatable: truncatableReducer,
|
||||||
cssVariables: cssVariablesReducer,
|
cssVariables: cssVariablesReducer,
|
||||||
|
@@ -145,6 +145,10 @@ import { ListableObjectDirective } from './object-collection/shared/listable-obj
|
|||||||
import { CommunitySearchResultGridElementComponent } from './object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component';
|
import { CommunitySearchResultGridElementComponent } from './object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component';
|
||||||
import { CollectionSearchResultGridElementComponent } from './object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component';
|
import { CollectionSearchResultGridElementComponent } from './object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component';
|
||||||
import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.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 = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -228,6 +232,10 @@ const COMPONENTS = [
|
|||||||
ObjectCollectionComponent,
|
ObjectCollectionComponent,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
SearchFormComponent,
|
SearchFormComponent,
|
||||||
|
PageWithSidebarComponent,
|
||||||
|
SidebarDropdownComponent,
|
||||||
|
SidebarFilterComponent,
|
||||||
|
SidebarFilterSelectedOptionComponent,
|
||||||
ThumbnailComponent,
|
ThumbnailComponent,
|
||||||
GridThumbnailComponent,
|
GridThumbnailComponent,
|
||||||
UploaderComponent,
|
UploaderComponent,
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
<a class="d-flex flex-row" (click)="click.emit($event)">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
|
||||||
|
<span class="filter-value pl-1 text-capitalize">{{label}}</span>
|
||||||
|
</label>
|
||||||
|
</a>
|
@@ -0,0 +1,11 @@
|
|||||||
|
a {
|
||||||
|
color: $body-color;
|
||||||
|
|
||||||
|
&:hover, &focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.badge {
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
}
|
@@ -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<any> = new EventEmitter<any>();
|
||||||
|
}
|
74
src/app/shared/sidebar/filter/sidebar-filter.actions.ts
Normal file
74
src/app/shared/sidebar/filter/sidebar-filter.actions.ts
Normal file
@@ -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 */
|
26
src/app/shared/sidebar/filter/sidebar-filter.component.html
Normal file
26
src/app/shared/sidebar/filter/sidebar-filter.component.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<div class="facet-filter d-block mb-3 p-3">
|
||||||
|
<div (click)="toggle()" class="filter-name">
|
||||||
|
<h5 class="d-inline-block mb-0">
|
||||||
|
{{ label | translate }}
|
||||||
|
</h5>
|
||||||
|
<span class="filter-toggle fas float-right"
|
||||||
|
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'">
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'"
|
||||||
|
(@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)"
|
||||||
|
class="sidebar-filter-wrapper" [ngClass]="{'closed' : closed}">
|
||||||
|
<div>
|
||||||
|
<div class="filters py-2">
|
||||||
|
<ng-template *ngIf="!singleValue">
|
||||||
|
<ds-sidebar-filter-selected-option
|
||||||
|
*ngFor="let value of (selectedValues | async)"
|
||||||
|
[label]="value"
|
||||||
|
(click)="removeValue.emit(value)">
|
||||||
|
</ds-sidebar-filter-selected-option>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
src/app/shared/sidebar/filter/sidebar-filter.component.scss
Normal file
12
src/app/shared/sidebar/filter/sidebar-filter.component.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
89
src/app/shared/sidebar/filter/sidebar-filter.component.ts
Normal file
89
src/app/shared/sidebar/filter/sidebar-filter.component.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
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],
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 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<string[]>;
|
||||||
|
@Output() removeValue:EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<boolean>;
|
||||||
|
|
||||||
|
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.closed = !this.expanded;
|
||||||
|
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<boolean>} Emits true when the current state of the filter is collapsed, false when it's expanded
|
||||||
|
*/
|
||||||
|
private isCollapsed():Observable<boolean> {
|
||||||
|
return this.filterService.isCollapsed(this.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
70
src/app/shared/sidebar/filter/sidebar-filter.reducer.ts
Normal file
70
src/app/shared/sidebar/filter/sidebar-filter.reducer.ts
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
90
src/app/shared/sidebar/filter/sidebar-filter.service.ts
Normal file
90
src/app/shared/sidebar/filter/sidebar-filter.service.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that performs all actions that have to do with sidebar filters like collapsing or expanding them.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class SidebarFilterService {
|
||||||
|
|
||||||
|
constructor(private store:Store<SidebarFilterState>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<boolean>} Emits the current collapsed state of the given filter, if it's unavailable, return false
|
||||||
|
*/
|
||||||
|
isCollapsed(filterName:string):Observable<boolean> {
|
||||||
|
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<SidebarFiltersState, SidebarFilterState> {
|
||||||
|
return keySelector<SidebarFilterState>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keySelector<T>(key:string):MemoizedSelector<SidebarFiltersState, T> {
|
||||||
|
return createSelector(filterStateSelector, (state:SidebarFilterState) => {
|
||||||
|
if (hasValue(state)) {
|
||||||
|
return state[key];
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
14
src/app/shared/sidebar/page-with-sidebar.component.html
Normal file
14
src/app/shared/sidebar/page-with-sidebar.component.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row-with-sidebar row-offcanvas row-offcanvas-left"
|
||||||
|
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
||||||
|
<div id="{{id}}-sidebar-content"
|
||||||
|
class="col-12 col-md-{{sideBarWidth}} sidebar-content {{sidebarClasses | async}}">
|
||||||
|
<ng-container *ngTemplateOutlet="sidebarContent"></ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-{{12 - sideBarWidth}}">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
52
src/app/shared/sidebar/page-with-sidebar.component.scss
Normal file
52
src/app/shared/sidebar/page-with-sidebar.component.scss
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
75
src/app/shared/sidebar/page-with-sidebar.component.spec.ts
Normal file
75
src/app/shared/sidebar/page-with-sidebar.component.spec.ts
Normal file
@@ -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.service';
|
||||||
|
import { HostWindowService } from '../host-window.service';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
|
describe('PageWithSidebarComponent', () => {
|
||||||
|
let comp:PageWithSidebarComponent;
|
||||||
|
let fixture:ComponentFixture<PageWithSidebarComponent>;
|
||||||
|
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
77
src/app/shared/sidebar/page-with-sidebar.component.ts
Normal file
77
src/app/shared/sidebar/page-with-sidebar.component.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { Component, Input, OnInit, TemplateRef } from '@angular/core';
|
||||||
|
import { SidebarService } from './sidebar.service';
|
||||||
|
import { HostWindowService } from '../host-window.service';
|
||||||
|
import { 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],
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 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<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits true if were on a small screen
|
||||||
|
*/
|
||||||
|
isXsOrSm$:Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The width of the sidebar (bootstrap columns)
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
sideBarWidth = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable for whether or not the sidebar is currently collapsed
|
||||||
|
*/
|
||||||
|
isSidebarCollapsed$:Observable<boolean>;
|
||||||
|
|
||||||
|
sidebarClasses:Observable<string>;
|
||||||
|
|
||||||
|
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<boolean>} emits true if the sidebar is currently collapsed, false if it is expanded
|
||||||
|
*/
|
||||||
|
private isSidebarCollapsed():Observable<boolean> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3,24 +3,24 @@ import { Observable } from 'rxjs';
|
|||||||
import { provideMockActions } from '@ngrx/effects/testing';
|
import { provideMockActions } from '@ngrx/effects/testing';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
import { SidebarCollapseAction } from './sidebar.actions';
|
||||||
import { SearchSidebarEffects } from './search-sidebar.effects';
|
import { SidebarEffects } from './sidebar-effects.service';
|
||||||
|
|
||||||
describe('SearchSidebarEffects', () => {
|
describe('SidebarEffects', () => {
|
||||||
let sidebarEffects: SearchSidebarEffects;
|
let sidebarEffects: SidebarEffects;
|
||||||
let actions: Observable<any>;
|
let actions: Observable<any>;
|
||||||
const dummyURL = 'http://f4fb15e2-1bd3-4e63-8d0d-486ad8bc714a';
|
const dummyURL = 'http://f4fb15e2-1bd3-4e63-8d0d-486ad8bc714a';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
SearchSidebarEffects,
|
SidebarEffects,
|
||||||
provideMockActions(() => actions),
|
provideMockActions(() => actions),
|
||||||
// other providers
|
// other providers
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
sidebarEffects = TestBed.get(SearchSidebarEffects);
|
sidebarEffects = TestBed.get(SidebarEffects);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('routeChange$', () => {
|
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', () => {
|
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}} } });
|
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);
|
expect(sidebarEffects.routeChange$).toBeObservable(expected);
|
||||||
});
|
});
|
6
src/app/shared/sidebar/sidebar-dropdown.component.html
Normal file
6
src/app/shared/sidebar/sidebar-dropdown.component.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="setting-option mb-3 p-3">
|
||||||
|
<h5><label for="{{id}}">{{label | translate}}</label></h5>
|
||||||
|
<select id="{{id}}" class="form-control" (change)="change.emit($event)">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</select>
|
||||||
|
</div>
|
3
src/app/shared/sidebar/sidebar-dropdown.component.scss
Normal file
3
src/app/shared/sidebar/sidebar-dropdown.component.scss
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.setting-option {
|
||||||
|
border: 1px solid map-get($theme-colors, light);
|
||||||
|
}
|
16
src/app/shared/sidebar/sidebar-dropdown.component.ts
Normal file
16
src/app/shared/sidebar/sidebar-dropdown.component.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sidebar-dropdown',
|
||||||
|
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;
|
||||||
|
@Output() change:EventEmitter<any> = new EventEmitter<number>();
|
||||||
|
}
|
@@ -3,14 +3,14 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Effect, Actions, ofType } from '@ngrx/effects'
|
import { Effect, Actions, ofType } from '@ngrx/effects'
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
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';
|
import { URLBaser } from '../../core/url-baser/url-baser';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes sure that if the user navigates to another route, the sidebar is collapsed
|
* Makes sure that if the user navigates to another route, the sidebar is collapsed
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchSidebarEffects {
|
export class SidebarEffects {
|
||||||
private previousPath: string;
|
private previousPath: string;
|
||||||
@Effect() routeChange$ = this.actions$
|
@Effect() routeChange$ = this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -19,7 +19,7 @@ export class SearchSidebarEffects {
|
|||||||
tap((action) => {
|
tap((action) => {
|
||||||
this.previousPath = this.getBaseUrl(action)
|
this.previousPath = this.getBaseUrl(action)
|
||||||
}),
|
}),
|
||||||
map(() => new SearchSidebarCollapseAction())
|
map(() => new SidebarCollapseAction())
|
||||||
);
|
);
|
||||||
|
|
||||||
constructor(private actions$: Actions) {
|
constructor(private actions$: Actions) {
|
@@ -1,6 +1,6 @@
|
|||||||
import { Action } from '@ngrx/store';
|
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
|
* 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
|
* literal types and runs a simple check to guarantee all
|
||||||
* action types in the application are unique.
|
* action types in the application are unique.
|
||||||
*/
|
*/
|
||||||
export const SearchSidebarActionTypes = {
|
export const SidebarActionTypes = {
|
||||||
COLLAPSE: type('dspace/search-sidebar/COLLAPSE'),
|
COLLAPSE: type('dspace/sidebar/COLLAPSE'),
|
||||||
EXPAND: type('dspace/search-sidebar/EXPAND'),
|
EXPAND: type('dspace/sidebar/EXPAND'),
|
||||||
TOGGLE: type('dspace/search-sidebar/TOGGLE')
|
TOGGLE: type('dspace/sidebar/TOGGLE')
|
||||||
};
|
};
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
/**
|
/**
|
||||||
* Used to collapse the sidebar
|
* Used to collapse the sidebar
|
||||||
*/
|
*/
|
||||||
export class SearchSidebarCollapseAction implements Action {
|
export class SidebarCollapseAction implements Action {
|
||||||
type = SearchSidebarActionTypes.COLLAPSE;
|
type = SidebarActionTypes.COLLAPSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to expand the sidebar
|
* Used to expand the sidebar
|
||||||
*/
|
*/
|
||||||
export class SearchSidebarExpandAction implements Action {
|
export class SidebarExpandAction implements Action {
|
||||||
type = SearchSidebarActionTypes.EXPAND;
|
type = SidebarActionTypes.EXPAND;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to collapse the sidebar when it's expanded and expand it when it's collapsed
|
* Used to collapse the sidebar when it's expanded and expand it when it's collapsed
|
||||||
*/
|
*/
|
||||||
export class SearchSidebarToggleAction implements Action {
|
export class SidebarToggleAction implements Action {
|
||||||
type = SearchSidebarActionTypes.TOGGLE;
|
type = SidebarActionTypes.TOGGLE;
|
||||||
}
|
}
|
||||||
/* tslint:enable:max-classes-per-file */
|
/* 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
|
* Export a type alias of all actions in this action group
|
||||||
* so that reducers can easily compose action types
|
* so that reducers can easily compose action types
|
||||||
*/
|
*/
|
||||||
export type SearchSidebarAction
|
export type SidebarAction
|
||||||
= SearchSidebarCollapseAction
|
= SidebarCollapseAction
|
||||||
| SearchSidebarExpandAction
|
| SidebarExpandAction
|
||||||
| SearchSidebarToggleAction
|
| SidebarToggleAction
|
@@ -1,12 +1,12 @@
|
|||||||
import * as deepFreeze from 'deep-freeze';
|
import * as deepFreeze from 'deep-freeze';
|
||||||
|
|
||||||
import { sidebarReducer } from './search-sidebar.reducer';
|
import { sidebarReducer } from './sidebar.reducer';
|
||||||
import {
|
import {
|
||||||
SearchSidebarCollapseAction, SearchSidebarExpandAction,
|
SidebarCollapseAction, SidebarExpandAction,
|
||||||
SearchSidebarToggleAction
|
SidebarToggleAction
|
||||||
} from './search-sidebar.actions';
|
} from './sidebar.actions';
|
||||||
|
|
||||||
class NullAction extends SearchSidebarCollapseAction {
|
class NullAction extends SidebarCollapseAction {
|
||||||
type = null;
|
type = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -34,7 +34,7 @@ describe('sidebarReducer', () => {
|
|||||||
|
|
||||||
it('should set sidebarCollapsed to true in response to the COLLAPSE action', () => {
|
it('should set sidebarCollapsed to true in response to the COLLAPSE action', () => {
|
||||||
const state = { sidebarCollapsed: false };
|
const state = { sidebarCollapsed: false };
|
||||||
const action = new SearchSidebarCollapseAction();
|
const action = new SidebarCollapseAction();
|
||||||
const newState = sidebarReducer(state, action);
|
const newState = sidebarReducer(state, action);
|
||||||
|
|
||||||
expect(newState.sidebarCollapsed).toEqual(true);
|
expect(newState.sidebarCollapsed).toEqual(true);
|
||||||
@@ -44,7 +44,7 @@ describe('sidebarReducer', () => {
|
|||||||
const state = { sidebarCollapsed: false };
|
const state = { sidebarCollapsed: false };
|
||||||
deepFreeze([state]);
|
deepFreeze([state]);
|
||||||
|
|
||||||
const action = new SearchSidebarCollapseAction();
|
const action = new SidebarCollapseAction();
|
||||||
sidebarReducer(state, action);
|
sidebarReducer(state, action);
|
||||||
|
|
||||||
// no expect required, deepFreeze will ensure an exception is thrown if the state
|
// 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', () => {
|
it('should set sidebarCollapsed to false in response to the EXPAND action', () => {
|
||||||
const state = { sidebarCollapsed: true };
|
const state = { sidebarCollapsed: true };
|
||||||
const action = new SearchSidebarExpandAction();
|
const action = new SidebarExpandAction();
|
||||||
const newState = sidebarReducer(state, action);
|
const newState = sidebarReducer(state, action);
|
||||||
|
|
||||||
expect(newState.sidebarCollapsed).toEqual(false);
|
expect(newState.sidebarCollapsed).toEqual(false);
|
||||||
@@ -63,13 +63,13 @@ describe('sidebarReducer', () => {
|
|||||||
const state = { sidebarCollapsed: true };
|
const state = { sidebarCollapsed: true };
|
||||||
deepFreeze([state]);
|
deepFreeze([state]);
|
||||||
|
|
||||||
const action = new SearchSidebarExpandAction();
|
const action = new SidebarExpandAction();
|
||||||
sidebarReducer(state, action);
|
sidebarReducer(state, action);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should flip the value of sidebarCollapsed in response to the TOGGLE action', () => {
|
it('should flip the value of sidebarCollapsed in response to the TOGGLE action', () => {
|
||||||
const state1 = { sidebarCollapsed: true };
|
const state1 = { sidebarCollapsed: true };
|
||||||
const action = new SearchSidebarToggleAction();
|
const action = new SidebarToggleAction();
|
||||||
|
|
||||||
const state2 = sidebarReducer(state1, action);
|
const state2 = sidebarReducer(state1, action);
|
||||||
const state3 = sidebarReducer(state2, action);
|
const state3 = sidebarReducer(state2, action);
|
||||||
@@ -82,7 +82,7 @@ describe('sidebarReducer', () => {
|
|||||||
const state = { sidebarCollapsed: true };
|
const state = { sidebarCollapsed: true };
|
||||||
deepFreeze([state]);
|
deepFreeze([state]);
|
||||||
|
|
||||||
const action = new SearchSidebarToggleAction();
|
const action = new SidebarToggleAction();
|
||||||
sidebarReducer(state, action);
|
sidebarReducer(state, action);
|
||||||
});
|
});
|
||||||
|
|
47
src/app/shared/sidebar/sidebar.reducer.ts
Normal file
47
src/app/shared/sidebar/sidebar.reducer.ts
Normal file
@@ -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 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,13 +1,13 @@
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { SearchSidebarService } from './search-sidebar.service';
|
import { SidebarService } from './sidebar.service';
|
||||||
import { AppState } from '../../app.reducer';
|
import { AppState } from '../../app.reducer';
|
||||||
import { async, TestBed } from '@angular/core/testing';
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
|
import { SidebarCollapseAction, SidebarExpandAction } from './sidebar.actions';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../host-window.service';
|
||||||
|
|
||||||
describe('SearchSidebarService', () => {
|
describe('SidebarService', () => {
|
||||||
let service: SearchSidebarService;
|
let service: SidebarService;
|
||||||
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
||||||
/* tslint:disable:no-empty */
|
/* tslint:disable:no-empty */
|
||||||
dispatch: {},
|
dispatch: {},
|
||||||
@@ -35,7 +35,7 @@ describe('SearchSidebarService', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service = new SearchSidebarService(store, windowService);
|
service = new SidebarService(store, windowService);
|
||||||
}) ;
|
}) ;
|
||||||
|
|
||||||
describe('when the collapse method is triggered', () => {
|
describe('when the collapse method is triggered', () => {
|
||||||
@@ -43,8 +43,8 @@ describe('SearchSidebarService', () => {
|
|||||||
service.collapse();
|
service.collapse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SearchSidebarCollapseAction should be dispatched to the store', () => {
|
it('SidebarCollapseAction should be dispatched to the store', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarCollapseAction());
|
expect(store.dispatch).toHaveBeenCalledWith(new SidebarCollapseAction());
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -54,8 +54,8 @@ describe('SearchSidebarService', () => {
|
|||||||
service.expand();
|
service.expand();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SearchSidebarExpandAction should be dispatched to the store', () => {
|
it('SidebarExpandAction should be dispatched to the store', () => {
|
||||||
expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarExpandAction());
|
expect(store.dispatch).toHaveBeenCalledWith(new SidebarExpandAction());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@@ -1,20 +1,20 @@
|
|||||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SearchSidebarState } from './search-sidebar.reducer';
|
import { SidebarState } from './sidebar.reducer';
|
||||||
import { createSelector, select, Store } from '@ngrx/store';
|
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 { AppState } from '../../app.reducer';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../host-window.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
const sidebarStateSelector = (state: AppState) => state.searchSidebar;
|
const sidebarStateSelector = (state: AppState) => state.sidebar;
|
||||||
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
|
* Service that performs all actions that have to do with the sidebar
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchSidebarService {
|
export class SidebarService {
|
||||||
/**
|
/**
|
||||||
* Emits true is the current screen size is mobile
|
* Emits true is the current screen size is mobile
|
||||||
*/
|
*/
|
||||||
@@ -47,13 +47,13 @@ export class SearchSidebarService {
|
|||||||
* Dispatches a collapse action to the store
|
* Dispatches a collapse action to the store
|
||||||
*/
|
*/
|
||||||
public collapse(): void {
|
public collapse(): void {
|
||||||
this.store.dispatch(new SearchSidebarCollapseAction());
|
this.store.dispatch(new SidebarCollapseAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches an expand action to the store
|
* Dispatches an expand action to the store
|
||||||
*/
|
*/
|
||||||
public expand(): void {
|
public expand(): void {
|
||||||
this.store.dispatch(new SearchSidebarExpandAction());
|
this.store.dispatch(new SidebarExpandAction());
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user