mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
44988: added reducer, angular animation, tests...
This commit is contained in:
@@ -1,10 +1,8 @@
|
|||||||
<div class="search-page">
|
<div class="search-page">
|
||||||
|
<ds-search-sidebar dsStick *ngIf="!(isMobileView | async)" class="col-3 sidebar-md-fixed" id="search-sidebar"
|
||||||
|
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
||||||
|
(toggleSidebar)="toggle()"></ds-search-sidebar>
|
||||||
<div id="search-header" class="row">
|
<div id="search-header" class="row">
|
||||||
<ds-layout-controls class="col-md-3 d-none d-md-block sidebar-md-fixed"
|
|
||||||
[isList]="isListView"
|
|
||||||
(toggleList)="setListView($event)"></ds-layout-controls>
|
|
||||||
|
|
||||||
<ds-view-mode-switch></ds-view-mode-switch>
|
|
||||||
<ds-search-form id="search-form" class="col-12 col-md-9 ml-md-auto"
|
<ds-search-form id="search-form" class="col-12 col-md-9 ml-md-auto"
|
||||||
[query]="query"
|
[query]="query"
|
||||||
[scope]="scopeObject?.payload | async"
|
[scope]="scopeObject?.payload | async"
|
||||||
@@ -13,20 +11,18 @@
|
|||||||
</ds-search-form>
|
</ds-search-form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div id="search-body" class="row-offcanvas row-offcanvas-left" [ngClass]="{'active': isSidebarActive}">
|
<div id="search-body" class="row-offcanvas row-offcanvas-left" [@slideInOut]="(isSidebarCollapsed | async) ? 'collapsed' : 'expanded'">
|
||||||
<ds-search-sidebar class="col-12 col-md-3 sidebar-md-fixed" id="search-sidebar"
|
<ds-search-sidebar *ngIf="(isMobileView | async)" class="col-12" id="search-sidebar-xs"
|
||||||
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
resultCount="{{(results.pageInfo | async)?.totalElements}}"
|
||||||
(toggleSidebar)="setSidebarActive($event)"></ds-search-sidebar>
|
(toggleSidebar)="toggle()"></ds-search-sidebar>
|
||||||
<div id="search-content" class="col-12 col-md-9 ml-md-auto">
|
<div id="search-content" class="col-12 col-md-9 ml-md-auto">
|
||||||
<div class="d-block d-md-none search-controls clearfix">
|
<div class="d-block d-md-none search-controls clearfix">
|
||||||
|
|
||||||
<ds-view-mode-switch></ds-view-mode-switch>
|
<ds-view-mode-switch></ds-view-mode-switch>
|
||||||
<ds-layout-controls [isList]="isListView"
|
<button (click)="toggle()" aria-controls="#search-body"
|
||||||
(toggleList)="setListView($event)"></ds-layout-controls>
|
|
||||||
<button (click)="setSidebarActive(true)" aria-controls="#search-body"
|
|
||||||
class="btn btn-outline-primary float-right"><i
|
class="btn btn-outline-primary float-right"><i
|
||||||
class="fa fa-sliders"></i> {{"search.sidebar.open" |
|
class="fa fa-sliders"></i> {{"search.sidebar.open"
|
||||||
translate}}
|
| translate}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="results"
|
<ds-search-results [searchResults]="results"
|
||||||
|
@@ -22,63 +22,36 @@
|
|||||||
}
|
}
|
||||||
@include media-breakpoint-down(sm) {
|
@include media-breakpoint-down(sm) {
|
||||||
position: relative;
|
position: relative;
|
||||||
-webkit-transition: all .25s ease-out;
|
|
||||||
-o-transition: all .25s ease-out;
|
|
||||||
transition: all .25s ease-out;
|
|
||||||
|
|
||||||
&.row-offcanvas {
|
&.row-offcanvas {
|
||||||
position: relative;
|
position: relative;
|
||||||
-webkit-transition: all .25s ease-out;
|
|
||||||
-o-transition: all .25s ease-out;
|
|
||||||
transition: all .25s ease-out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.row-offcanvas-right {
|
&.row-offcanvas-right #search-sidebar-xs {
|
||||||
right: 0;
|
right: -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.row-offcanvas-left {
|
&.row-offcanvas-left #search-sidebar-xs {
|
||||||
left: 0;
|
left: -100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.row-offcanvas-right
|
#search-sidebar-xs {
|
||||||
#search-sidebar {
|
|
||||||
right: -100%; /* 12 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-offcanvas-right.active
|
|
||||||
#search-sidebar {
|
|
||||||
right: -100%; /* 6 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-offcanvas-left
|
|
||||||
#search-sidebar {
|
|
||||||
left: -100%; /* 12 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-offcanvas-left.active
|
|
||||||
#search-sidebar {
|
|
||||||
left: -100%; /* 6 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-offcanvas-right.active {
|
|
||||||
right: 100%; /* 6 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
&.row-offcanvas-left.active {
|
|
||||||
left: 100%; /* 6 columns */
|
|
||||||
}
|
|
||||||
|
|
||||||
#search-sidebar {
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: 100%; /* 6 columns */
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-md-fixed {
|
.sidebar-md-fixed {
|
||||||
@include media-breakpoint-up(md) {
|
@include media-breakpoint-up(md) {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
|
margin-top: -$content-spacing;
|
||||||
|
padding-top: $content-spacing;
|
||||||
|
&.stick {
|
||||||
|
top: 0;
|
||||||
|
margin-top: 0px;
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
@@ -8,8 +9,11 @@ import { SearchService } from './search-service/search.service';
|
|||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { SearchPageModule } from './search-page.module';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
|
||||||
describe('SearchPageComponent', () => {
|
fdescribe('SearchPageComponent', () => {
|
||||||
let comp: SearchPageComponent;
|
let comp: SearchPageComponent;
|
||||||
let fixture: ComponentFixture<SearchPageComponent>;
|
let fixture: ComponentFixture<SearchPageComponent>;
|
||||||
let searchServiceObject: SearchService;
|
let searchServiceObject: SearchService;
|
||||||
@@ -39,14 +43,15 @@ describe('SearchPageComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
// imports: [ SearchPageModule ],
|
imports: [ SearchPageModule ],
|
||||||
declarations: [SearchPageComponent],
|
// declarations: [SearchPageComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: searchServiceStub },
|
{ provide: SearchService, useValue: searchServiceStub },
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||||
{ provide: Router, useClass: RouterStub }
|
{ provide: Router, useClass: RouterStub },
|
||||||
],
|
{ provide: Store, useClass: {} }
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
@@ -6,11 +6,20 @@ import { SearchResult } from './search-result.model';
|
|||||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { ViewModeSwitchComponent } from '../shared/view-mode-switch/view-mode-switch.component';
|
|
||||||
import { SearchOptions } from './search-options.model';
|
import { SearchOptions } from './search-options.model';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
import { isNotEmpty } from '../shared/empty.util';
|
import { isNotEmpty } from '../shared/empty.util';
|
||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
|
import { createSelector, Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '../app.reducer';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { SearchSidebarState } from './search-sidebar/search-sidebar.reducer';
|
||||||
|
import { SearchSidebarToggleAction } from './search-sidebar/search-sidebar.actions';
|
||||||
|
import { slideInOut } from '../shared/animations/slide';
|
||||||
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
|
|
||||||
|
const sidebarStateSelector = (state: AppState) => state.searchSidebar;
|
||||||
|
const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SearchSidebarState) => sidebar.sidebarCollapsed);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -22,6 +31,7 @@ import { Community } from '../core/shared/community.model';
|
|||||||
selector: 'ds-search-page',
|
selector: 'ds-search-page',
|
||||||
styleUrls: ['./search-page.component.scss'],
|
styleUrls: ['./search-page.component.scss'],
|
||||||
templateUrl: './search-page.component.html',
|
templateUrl: './search-page.component.html',
|
||||||
|
animations: [slideInOut]
|
||||||
})
|
})
|
||||||
export class SearchPageComponent implements OnInit, OnDestroy {
|
export class SearchPageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@@ -34,14 +44,14 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
|||||||
currentParams = {};
|
currentParams = {};
|
||||||
searchOptions: SearchOptions;
|
searchOptions: SearchOptions;
|
||||||
scopeList: RemoteData<Community[]>;
|
scopeList: RemoteData<Community[]>;
|
||||||
isSidebarActive = false;
|
isSidebarCollapsed: Observable<boolean>;
|
||||||
isListView = true;
|
isMobileView: Observable<boolean>;
|
||||||
|
|
||||||
constructor(
|
constructor(private service: SearchService,
|
||||||
private service: SearchService,
|
private route: ActivatedRoute,
|
||||||
private route: ActivatedRoute,
|
private communityService: CommunityDataService,
|
||||||
private communityService: CommunityDataService
|
private store: Store<AppState>,
|
||||||
) {
|
private hostWindowService: HostWindowService) {
|
||||||
this.scopeList = communityService.findAll();
|
this.scopeList = communityService.findAll();
|
||||||
// Initial pagination config
|
// Initial pagination config
|
||||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||||
@@ -50,9 +60,14 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
|||||||
pagination.pageSize = 10;
|
pagination.pageSize = 10;
|
||||||
const sort: SortOptions = new SortOptions();
|
const sort: SortOptions = new SortOptions();
|
||||||
this.searchOptions = { pagination: pagination, sort: sort };
|
this.searchOptions = { pagination: pagination, sort: sort };
|
||||||
|
this.isMobileView = Observable.combineLatest(
|
||||||
|
this.hostWindowService.isXs(),
|
||||||
|
this.hostWindowService.isSm(),
|
||||||
|
(isXs, isSm) => isXs || isSm);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.isSidebarCollapsed = this.store.select(sidebarCollapsedSelector);
|
||||||
this.sub = this.route
|
this.sub = this.route
|
||||||
.queryParams
|
.queryParams
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
@@ -93,11 +108,7 @@ export class SearchPageComponent implements OnInit, OnDestroy {
|
|||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSidebarActive(show: boolean) {
|
public toggle(): void {
|
||||||
this.isSidebarActive = show;
|
this.store.dispatch(new SearchSidebarToggleAction());
|
||||||
}
|
|
||||||
|
|
||||||
setListView(isList: boolean) {
|
|
||||||
this.isListView = isList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,8 +19,6 @@ import { LayoutControlsComponent } from './layout-controls/layout-controls.compo
|
|||||||
imports: [
|
imports: [
|
||||||
SearchPageRoutingModule,
|
SearchPageRoutingModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
TranslateModule,
|
|
||||||
RouterModule,
|
|
||||||
SharedModule
|
SharedModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
import { type } from '../../shared/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 SearchSidebarActionTypes = {
|
||||||
|
COLLAPSE: type('dspace/search-sidebar/COLLAPSE'),
|
||||||
|
EXPAND: type('dspace/search-sidebar/EXPAND'),
|
||||||
|
TOGGLE: type('dspace/search-sidebar/TOGGLE')
|
||||||
|
};
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
export class SearchSidebarCollapseAction implements Action {
|
||||||
|
type = SearchSidebarActionTypes.COLLAPSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchSidebarExpandAction implements Action {
|
||||||
|
type = SearchSidebarActionTypes.EXPAND;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SearchSidebarToggleAction implements Action {
|
||||||
|
type = SearchSidebarActionTypes.TOGGLE;
|
||||||
|
}
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@@ -7,6 +7,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="search-sidebar-content">
|
<div id="search-sidebar-content">
|
||||||
|
<ds-view-mode-switch class="d-none d-md-block"></ds-view-mode-switch>
|
||||||
Place filters and other search config here
|
Place filters and other search config here
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Effect, Actions } from '@ngrx/effects'
|
||||||
|
import * as fromRouter from '@ngrx/router-store';
|
||||||
|
|
||||||
|
import { HostWindowActionTypes } from '../../shared/host-window.actions';
|
||||||
|
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SearchSidebarEffects {
|
||||||
|
|
||||||
|
@Effect() resize$ = this.actions$
|
||||||
|
.ofType(HostWindowActionTypes.RESIZE)
|
||||||
|
.map(() => new SearchSidebarCollapseAction());
|
||||||
|
|
||||||
|
@Effect() routeChange$ = this.actions$
|
||||||
|
.ofType(fromRouter.ROUTER_NAVIGATION)
|
||||||
|
.map(() => new SearchSidebarCollapseAction());
|
||||||
|
|
||||||
|
constructor(private actions$: Actions) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,91 @@
|
|||||||
|
import * as deepFreeze from 'deep-freeze';
|
||||||
|
|
||||||
|
import { sidebarReducer } from './search-sidebar.reducer';
|
||||||
|
import {
|
||||||
|
SearchSidebarCollapseAction, SearchSidebarExpandAction,
|
||||||
|
SearchSidebarToggleAction
|
||||||
|
} from './search-sidebar.actions';
|
||||||
|
import { async, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
class NullAction extends SearchSidebarCollapseAction {
|
||||||
|
type = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('sidebarReducer', () => {
|
||||||
|
|
||||||
|
it('should return the current state when no valid actions have been made', () => {
|
||||||
|
const state = { sidebarCollapsed: false };
|
||||||
|
const action = new NullAction();
|
||||||
|
const newState = sidebarReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should start with sidebarCollapsed = true', () => {
|
||||||
|
const action = new NullAction();
|
||||||
|
const initialState = sidebarReducer(undefined, action);
|
||||||
|
|
||||||
|
// The search sidebar starts collapsed
|
||||||
|
expect(initialState.sidebarCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set sidebarCollapsed to true in response to the COLLAPSE action', () => {
|
||||||
|
const state = { sidebarCollapsed: false };
|
||||||
|
const action = new SearchSidebarCollapseAction();
|
||||||
|
const newState = sidebarReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.sidebarCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform the COLLAPSE action without affecting the previous state', () => {
|
||||||
|
const state = { sidebarCollapsed: false };
|
||||||
|
deepFreeze([state]);
|
||||||
|
|
||||||
|
const action = new SearchSidebarCollapseAction();
|
||||||
|
sidebarReducer(state, action);
|
||||||
|
|
||||||
|
// no expect required, deepFreeze will ensure an exception is thrown if the state
|
||||||
|
// is mutated, and any uncaught exception will cause the test to fail
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set sidebarCollapsed to false in response to the EXPAND action', () => {
|
||||||
|
const state = { sidebarCollapsed: true };
|
||||||
|
const action = new SearchSidebarExpandAction();
|
||||||
|
const newState = sidebarReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.sidebarCollapsed).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform the EXPAND action without affecting the previous state', () => {
|
||||||
|
const state = { sidebarCollapsed: true };
|
||||||
|
deepFreeze([state]);
|
||||||
|
|
||||||
|
const action = new SearchSidebarExpandAction();
|
||||||
|
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 state2 = sidebarReducer(state1, action);
|
||||||
|
const state3 = sidebarReducer(state2, action);
|
||||||
|
|
||||||
|
expect(state2.sidebarCollapsed).toEqual(false);
|
||||||
|
expect(state3.sidebarCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should perform the TOGGLE action without affecting the previous state', () => {
|
||||||
|
const state = { sidebarCollapsed: true };
|
||||||
|
deepFreeze([state]);
|
||||||
|
|
||||||
|
const action = new SearchSidebarToggleAction();
|
||||||
|
sidebarReducer(state, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,38 @@
|
|||||||
|
import { SearchSidebarAction, SearchSidebarActionTypes } from './search-sidebar.actions';
|
||||||
|
|
||||||
|
export interface SearchSidebarState {
|
||||||
|
sidebarCollapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: SearchSidebarState = {
|
||||||
|
sidebarCollapsed: true
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -62,12 +62,17 @@ export class AppComponent implements OnInit {
|
|||||||
const env: string = this.config.production ? 'Production' : 'Development';
|
const env: string = this.config.production ? 'Production' : 'Development';
|
||||||
const color: string = this.config.production ? 'red' : 'green';
|
const color: string = this.config.production ? 'red' : 'green';
|
||||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||||
|
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
private onResize(event): void {
|
private onResize(event): void {
|
||||||
|
this.dispatchWindowSize(event.target.innerWidth, event.target.innerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatchWindowSize(width, height): void {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
new HostWindowResizeAction(event.target.innerWidth, event.target.innerHeight)
|
new HostWindowResizeAction(width, height)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,15 +3,21 @@ import * as fromRouter from '@ngrx/router-store';
|
|||||||
|
|
||||||
import { headerReducer, HeaderState } from './header/header.reducer';
|
import { headerReducer, HeaderState } from './header/header.reducer';
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||||
|
import {
|
||||||
|
SearchSidebarState,
|
||||||
|
sidebarReducer
|
||||||
|
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: fromRouter.RouterReducerState;
|
||||||
hostWindow: HostWindowState;
|
hostWindow: HostWindowState;
|
||||||
header: HeaderState;
|
header: HeaderState;
|
||||||
|
searchSidebar: SearchSidebarState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
router: fromRouter.routerReducer,
|
router: fromRouter.routerReducer,
|
||||||
hostWindow: hostWindowReducer,
|
hostWindow: hostWindowReducer,
|
||||||
header: headerReducer
|
header: headerReducer,
|
||||||
|
searchSidebar: sidebarReducer,
|
||||||
};
|
};
|
||||||
|
16
src/app/shared/animations/slide.ts
Normal file
16
src/app/shared/animations/slide.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { animate, state, transition, trigger, style } from '@angular/animations';
|
||||||
|
|
||||||
|
export const slideInOut = trigger('slideInOut', [
|
||||||
|
|
||||||
|
/*
|
||||||
|
state('expanded', style({ right: '100%' }));
|
||||||
|
|
||||||
|
state('collapsed', style({ right: 0 }));
|
||||||
|
*/
|
||||||
|
|
||||||
|
state('expanded', style({ left: '100%' })),
|
||||||
|
|
||||||
|
state('collapsed', style({ left: 0 })),
|
||||||
|
|
||||||
|
transition('expanded <=> collapsed', animate(250)),
|
||||||
|
]);
|
@@ -16,4 +16,8 @@ export class MockHostWindowService {
|
|||||||
isXs(): Observable<boolean> {
|
isXs(): Observable<boolean> {
|
||||||
return Observable.of(this.width < 576);
|
return Observable.of(this.width < 576);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSm(): Observable<boolean> {
|
||||||
|
return Observable.of(this.width < 768);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ import { SearchResultListElementComponent } from '../object-list/search-result-l
|
|||||||
import { SearchFormComponent } from './search-form/search-form.component';
|
import { SearchFormComponent } from './search-form/search-form.component';
|
||||||
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
|
import { WrapperListElementComponent } from '../object-list/wrapper-list-element/wrapper-list-element.component';
|
||||||
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
import { ViewModeSwitchComponent } from './view-mode-switch/view-mode-switch.component';
|
||||||
|
import { ScrollAndStickDirective } from './utils/scroll-and-stick.directive';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
|
||||||
@@ -66,6 +67,10 @@ const COMPONENTS = [
|
|||||||
ViewModeSwitchComponent
|
ViewModeSwitchComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const DIRECTIVES = [
|
||||||
|
ScrollAndStickDirective,
|
||||||
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put shared entry components (components that are created dynamically) here
|
// put shared entry components (components that are created dynamically) here
|
||||||
CollectionListElementComponent,
|
CollectionListElementComponent,
|
||||||
@@ -81,12 +86,14 @@ const ENTRY_COMPONENTS = [
|
|||||||
declarations: [
|
declarations: [
|
||||||
...PIPES,
|
...PIPES,
|
||||||
...COMPONENTS,
|
...COMPONENTS,
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS,
|
||||||
|
...DIRECTIVES
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
...MODULES,
|
...MODULES,
|
||||||
...PIPES,
|
...PIPES,
|
||||||
...COMPONENTS
|
...COMPONENTS,
|
||||||
|
...DIRECTIVES
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
|
40
src/app/shared/utils/scroll-and-stick.directive.ts
Normal file
40
src/app/shared/utils/scroll-and-stick.directive.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import { NativeWindowRef, NativeWindowService } from '../window.service';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { AfterViewInit, Directive, ElementRef, Inject } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[dsStick]'
|
||||||
|
})
|
||||||
|
export class ScrollAndStickDirective implements AfterViewInit {
|
||||||
|
|
||||||
|
private initialY: number;
|
||||||
|
|
||||||
|
constructor(private _element: ElementRef, @Inject(NativeWindowService) private _window: NativeWindowRef) {
|
||||||
|
this.subscribeForScrollEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.initialY = this._element.nativeElement.getBoundingClientRect().top;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeForScrollEvent() {
|
||||||
|
|
||||||
|
const obs = Observable.fromEvent(window, 'scroll');
|
||||||
|
|
||||||
|
obs.subscribe((e) => this.handleScrollEvent(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScrollEvent(e) {
|
||||||
|
|
||||||
|
if (this._window.nativeWindow.pageYOffset >= this.initialY) {
|
||||||
|
|
||||||
|
this._element.nativeElement.classList.add('stick');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this._element.nativeElement.classList.remove('stick');
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user