mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 02:24:11 +00:00
Implement sidebar filter and dropdown components
This commit is contained in:
@@ -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>
|
||||||
|
@@ -34,6 +34,7 @@ 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 = [
|
||||||
SidebarEffects
|
SidebarEffects
|
||||||
@@ -78,6 +79,7 @@ const components = [
|
|||||||
declarations: components,
|
declarations: components,
|
||||||
providers: [
|
providers: [
|
||||||
SidebarService,
|
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>
|
@@ -1,4 +1,4 @@
|
|||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
import { ActionReducerMap, createSelector, MemoizedSelector, State } from '@ngrx/store';
|
||||||
import * as fromRouter from '@ngrx/router-store';
|
import * 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';
|
||||||
@@ -6,6 +6,10 @@ import {
|
|||||||
SidebarState,
|
SidebarState,
|
||||||
sidebarReducer
|
sidebarReducer
|
||||||
} from './shared/sidebar/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: SidebarState;
|
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,
|
||||||
|
@@ -148,6 +148,9 @@ import { PublicationGridElementComponent } from './object-grid/item-grid-element
|
|||||||
import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
|
import { ItemTypeBadgeComponent } from './object-list/item-type-badge/item-type-badge.component';
|
||||||
import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
import { ItemMetadataRepresentationListElementComponent } from './object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
||||||
import { PageWithSidebarComponent } from './sidebar/page-with-sidebar.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
|
||||||
@@ -235,6 +238,9 @@ const COMPONENTS = [
|
|||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
SearchFormComponent,
|
SearchFormComponent,
|
||||||
PageWithSidebarComponent,
|
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 */
|
24
src/app/shared/sidebar/filter/sidebar-filter.component.html
Normal file
24
src/app/shared/sidebar/filter/sidebar-filter.component.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<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">
|
||||||
|
<ds-sidebar-filter-selected-option
|
||||||
|
*ngFor="let value of (selectedValues | async)"
|
||||||
|
[label]="value"
|
||||||
|
(click)="removeValue.emit(value)">
|
||||||
|
</ds-sidebar-filter-selected-option>
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
}
|
84
src/app/shared/sidebar/filter/sidebar-filter.component.ts
Normal file
84
src/app/shared/sidebar/filter/sidebar-filter.component.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { SidebarFilterService } from './sidebar-filter.service';
|
||||||
|
import { slide } from '../../animations/slide';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sidebar-filter',
|
||||||
|
styleUrls: ['./sidebar-filter.component.scss'],
|
||||||
|
templateUrl: './sidebar-filter.component.html',
|
||||||
|
animations: [slide],
|
||||||
|
})
|
||||||
|
export class SidebarFilterComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() name:string;
|
||||||
|
@Input() type:string;
|
||||||
|
@Input() label:string;
|
||||||
|
@Input() expanded = true;
|
||||||
|
@Input() selectedValues:Observable<string[]>;
|
||||||
|
@Output() submitValue:EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
@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.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
87
src/app/shared/sidebar/filter/sidebar-filter.service.ts
Normal file
87
src/app/shared/sidebar/filter/sidebar-filter.service.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
FilterCollapseAction,
|
||||||
|
FilterExpandAction, FilterInitializeAction,
|
||||||
|
FilterToggleAction
|
||||||
|
} from './sidebar-filter.actions';
|
||||||
|
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||||
|
import { SidebarFiltersState, SidebarFilterState } from './sidebar-filter.reducer';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, map } from 'rxjs/operators';
|
||||||
|
import { hasValue } from '../../empty.util';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SidebarFilterService {
|
||||||
|
|
||||||
|
constructor(private store:Store<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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -2,8 +2,8 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { PageWithSidebarComponent } from './page-with-sidebar.component';
|
import { PageWithSidebarComponent } from './page-with-sidebar.component';
|
||||||
import { SidebarService } from './sidebar/sidebar.service';
|
import { SidebarService } from './sidebar.service';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../host-window.service';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
describe('PageWithSidebarComponent', () => {
|
describe('PageWithSidebarComponent', () => {
|
||||||
|
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);
|
||||||
|
}
|
12
src/app/shared/sidebar/sidebar-dropdown.component.ts
Normal file
12
src/app/shared/sidebar/sidebar-dropdown.component.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sidebar-dropdown',
|
||||||
|
styleUrls: ['./sidebar-dropdown.component.scss'],
|
||||||
|
templateUrl: './sidebar-dropdown.component.html',
|
||||||
|
})
|
||||||
|
export class SidebarDropdownComponent {
|
||||||
|
@Input() id:string;
|
||||||
|
@Input() label:string;
|
||||||
|
@Output() change:EventEmitter<any> = new EventEmitter<number>();
|
||||||
|
}
|
@@ -12,7 +12,7 @@ const initialState: SidebarState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a search sidebar action on the current state
|
* Performs a sidebar action on the current state
|
||||||
* @param {SidebarState} state The state before the action is performed
|
* @param {SidebarState} state The state before the action is performed
|
||||||
* @param {SidebarAction} action The action that should be performed
|
* @param {SidebarAction} action The action that should be performed
|
||||||
* @returns {SidebarState} The state after the action is performed
|
* @returns {SidebarState} The state after the action is performed
|
||||||
|
@@ -7,7 +7,7 @@ import { AppState } from '../../app.reducer';
|
|||||||
import { HostWindowService } from '../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: SidebarState) => sidebar.sidebarCollapsed);
|
const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SidebarState) => sidebar.sidebarCollapsed);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user