mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 15:03:07 +00:00
Fixed issue #1 and some other fixes
This commit is contained in:
@@ -2,7 +2,6 @@ import 'rxjs/add/observable/of';
|
|||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, 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 { PaginatedSearchOptions } from './paginated-search-options.model';
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
import { ViewMode } from './search-options.model';
|
|
||||||
|
|
||||||
describe('PaginatedSearchOptions', () => {
|
describe('PaginatedSearchOptions', () => {
|
||||||
let options: PaginatedSearchOptions;
|
let options: PaginatedSearchOptions;
|
||||||
@@ -19,7 +18,6 @@ describe('PaginatedSearchOptions', () => {
|
|||||||
options.filters = filters;
|
options.filters = filters;
|
||||||
options.query = query;
|
options.query = query;
|
||||||
options.scope = scope;
|
options.scope = scope;
|
||||||
options.view = ViewMode.Grid;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when toRestUrl is called', () => {
|
describe('when toRestUrl is called', () => {
|
||||||
|
@@ -91,8 +91,6 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
fixture = TestBed.createComponent(SearchFacetFilterComponent);
|
fixture = TestBed.createComponent(SearchFacetFilterComponent);
|
||||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||||
comp.filterConfig = mockFilterConfig;
|
comp.filterConfig = mockFilterConfig;
|
||||||
comp.filterValues = [mockValues];
|
|
||||||
// comp.filterValues$ = new BehaviorSubject({});
|
|
||||||
filterService = (comp as any).filterService;
|
filterService = (comp as any).filterService;
|
||||||
searchService = (comp as any).searchService;
|
searchService = (comp as any).searchService;
|
||||||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues);
|
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues);
|
||||||
@@ -198,10 +196,9 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('when updateFilterValueList is called', () => {
|
describe('when updateFilterValueList is called', () => {
|
||||||
const searchOptions = new SearchOptions();
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(comp, 'showFirstPageOnly');
|
spyOn(comp, 'showFirstPageOnly');
|
||||||
comp.updateFilterValueList(searchOptions)
|
comp.updateFilterValueList()
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call showFirstPageOnly and empty the filter', () => {
|
it('should call showFirstPageOnly and empty the filter', () => {
|
||||||
@@ -211,7 +208,6 @@ describe('SearchFacetFilterComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('when findSuggestions is called with query \'test\'', () => {
|
describe('when findSuggestions is called with query \'test\'', () => {
|
||||||
const query = 'test';
|
const query = 'test';
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -189,6 +189,9 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
|
|||||||
this.filter = data;
|
this.filter = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For usage of the hasValue function in the template
|
||||||
|
*/
|
||||||
hasValue(o: any): boolean {
|
hasValue(o: any): boolean {
|
||||||
return hasValue(o);
|
return hasValue(o);
|
||||||
}
|
}
|
||||||
|
@@ -248,7 +248,7 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a expand action to the store for a given filter
|
* Dispatches an expand action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {string} filterName The filter for which the action is dispatched
|
||||||
*/
|
*/
|
||||||
public expand(filterName: string): void {
|
public expand(filterName: string): void {
|
||||||
@@ -264,7 +264,7 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a initial collapse action to the store for a given filter
|
* Dispatches an initial collapse action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {string} filterName The filter for which the action is dispatched
|
||||||
*/
|
*/
|
||||||
public initialCollapse(filterName: string): void {
|
public initialCollapse(filterName: string): void {
|
||||||
@@ -272,7 +272,7 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a initial expand action to the store for a given filter
|
* Dispatches an initial expand action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {string} filterName The filter for which the action is dispatched
|
||||||
*/
|
*/
|
||||||
public initialExpand(filterName: string): void {
|
public initialExpand(filterName: string): void {
|
||||||
@@ -288,7 +288,7 @@ export class SearchFilterService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a increment page action to the store for a given filter
|
* Dispatches an increment page action to the store for a given filter
|
||||||
* @param {string} filterName The filter for which the action is dispatched
|
* @param {string} filterName The filter for which the action is dispatched
|
||||||
*/
|
*/
|
||||||
public incrementPage(filterName: string): void {
|
public incrementPage(filterName: string): void {
|
||||||
|
@@ -96,7 +96,6 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(SearchRangeFilterComponent);
|
fixture = TestBed.createComponent(SearchRangeFilterComponent);
|
||||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||||
comp.filterValues = [mockValues];
|
|
||||||
filterService = (comp as any).filterService;
|
filterService = (comp as any).filterService;
|
||||||
searchService = (comp as any).searchService;
|
searchService = (comp as any).searchService;
|
||||||
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues);
|
spyOn(searchService, 'getFacetValuesFor').and.returnValue(mockValues);
|
||||||
@@ -104,9 +103,9 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the getAddParams method is called wih a value', () => {
|
describe('when the getChangeParams method is called wih a value', () => {
|
||||||
it('should return the selectedValue list with the new parameter value', () => {
|
it('should return the selectedValue list with the new parameter value', () => {
|
||||||
const result$ = comp.getAddParams(value3);
|
const result$ = comp.getChangeParams(value3);
|
||||||
result$.subscribe((result) => {
|
result$.subscribe((result) => {
|
||||||
expect(result[mockFilterConfig.paramName + minSuffix]).toEqual(['1990']);
|
expect(result[mockFilterConfig.paramName + minSuffix]).toEqual(['1990']);
|
||||||
expect(result[mockFilterConfig.paramName + maxSuffix]).toEqual(['1992']);
|
expect(result[mockFilterConfig.paramName + maxSuffix]).toEqual(['1992']);
|
||||||
@@ -114,16 +113,6 @@ describe('SearchRangeFilterComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the getRemoveParams method is called wih a value', () => {
|
|
||||||
it('should return the selectedValue list with the parameter value left out', () => {
|
|
||||||
const result$ = comp.getRemoveParams(value1);
|
|
||||||
result$.subscribe((result) => {
|
|
||||||
expect(result[mockFilterConfig.paramName + minSuffix]).toBeNull();
|
|
||||||
expect(result[mockFilterConfig.paramName + maxSuffix]).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the onSubmit method is called with data', () => {
|
describe('when the onSubmit method is called with data', () => {
|
||||||
const searchUrl = '/search/path';
|
const searchUrl = '/search/path';
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import 'rxjs/add/observable/of';
|
import 'rxjs/add/observable/of';
|
||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||||
import { SearchOptions, ViewMode } from './search-options.model';
|
import { SearchOptions } from './search-options.model';
|
||||||
|
|
||||||
describe('SearchOptions', () => {
|
describe('SearchOptions', () => {
|
||||||
let options: PaginatedSearchOptions;
|
let options: PaginatedSearchOptions;
|
||||||
@@ -13,7 +13,6 @@ describe('SearchOptions', () => {
|
|||||||
options.filters = filters;
|
options.filters = filters;
|
||||||
options.query = query;
|
options.query = query;
|
||||||
options.scope = scope;
|
options.scope = scope;
|
||||||
options.view = ViewMode.Grid;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when toRestUrl is called', () => {
|
describe('when toRestUrl is called', () => {
|
||||||
|
@@ -2,20 +2,10 @@ import { isNotEmpty } from '../shared/empty.util';
|
|||||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||||
import 'core-js/library/fn/object/entries';
|
import 'core-js/library/fn/object/entries';
|
||||||
|
|
||||||
/**
|
|
||||||
* This enumeration represents all possible ways of representing the a group of objects
|
|
||||||
*/
|
|
||||||
|
|
||||||
export enum ViewMode {
|
|
||||||
List = 'list',
|
|
||||||
Grid = 'grid'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This model class represents all parameters needed to request information about a certain search request
|
* This model class represents all parameters needed to request information about a certain search request
|
||||||
*/
|
*/
|
||||||
export class SearchOptions {
|
export class SearchOptions {
|
||||||
view?: ViewMode = ViewMode.List;
|
|
||||||
scope?: string;
|
scope?: string;
|
||||||
query?: string;
|
query?: string;
|
||||||
filters?: any;
|
filters?: any;
|
||||||
|
@@ -31,7 +31,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-search-results [searchResults]="resultsRD$ | async"
|
<ds-search-results [searchResults]="resultsRD$ | async"
|
||||||
[searchConfig]="searchOptions$ | async" [sortConfig]="sortConfig"></ds-search-results>
|
[searchConfig]="searchOptions$ | async"></ds-search-results>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -12,9 +12,9 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte
|
|||||||
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 { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||||
import { Subject } from 'rxjs/Subject';
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -38,13 +38,12 @@ export class SearchPageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The current search results
|
* The current search results
|
||||||
*/
|
*/
|
||||||
resultsRD$: Subject<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> = new Subject();
|
resultsRD$: BehaviorSubject<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> = new BehaviorSubject(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current paginated search options
|
* The current paginated search options
|
||||||
*/
|
*/
|
||||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||||
sortConfig: SortOptions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current relevant scopes
|
* The current relevant scopes
|
||||||
@@ -91,8 +90,7 @@ export class SearchPageComponent implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchOptions$ = this.filterService.getPaginatedSearchOptions(this.defaults);
|
this.searchOptions$ = this.filterService.getPaginatedSearchOptions(this.defaults);
|
||||||
this.sub = this.searchOptions$.subscribe((searchOptions) =>
|
this.sub = this.searchOptions$.subscribe((searchOptions) =>
|
||||||
this.service.search(searchOptions).first().subscribe((results) => this.resultsRD$.next(results))
|
this.service.search(searchOptions).filter((rd) => !rd.isLoading).first().subscribe((results) => this.resultsRD$.next(results)));
|
||||||
);
|
|
||||||
|
|
||||||
this.scopeListRD$ = this.filterService.getCurrentScope().pipe(
|
this.scopeListRD$ = this.filterService.getCurrentScope().pipe(
|
||||||
flatMap((scopeId) => this.service.getScopes(scopeId))
|
flatMap((scopeId) => this.service.getScopes(scopeId))
|
||||||
@@ -107,7 +105,7 @@ export class SearchPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sidebar to a expanded state
|
* Set the sidebar to an expanded state
|
||||||
*/
|
*/
|
||||||
public openSidebar(): void {
|
public openSidebar(): void {
|
||||||
this.sidebarService.expand();
|
this.sidebarService.expand();
|
||||||
|
@@ -82,5 +82,9 @@ const effects = [
|
|||||||
SearchBooleanFilterComponent,
|
SearchBooleanFilterComponent,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components and pipes that are necessary for the search page
|
||||||
|
*/
|
||||||
export class SearchPageModule {
|
export class SearchPageModule {
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,10 @@ import { Component, Input } from '@angular/core';
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||||
import { SearchOptions, ViewMode } from '../search-options.model';
|
import { SearchOptions } from '../search-options.model';
|
||||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { SearchResult } from '../search-result.model';
|
import { SearchResult } from '../search-result.model';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-search-results',
|
selector: 'ds-search-results',
|
||||||
@@ -30,13 +30,9 @@ export class SearchResultsComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() searchConfig: SearchOptions;
|
@Input() searchConfig: SearchOptions;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current sort options for the search
|
|
||||||
*/
|
|
||||||
@Input() sortConfig: SortOptions;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current view mode for the search results
|
* The current view mode for the search results
|
||||||
*/
|
*/
|
||||||
@Input() viewMode: ViewMode;
|
@Input() viewMode: ViewMode;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { SearchService } from './search.service';
|
import { SearchService } from './search.service';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
||||||
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
|
import { ActivatedRoute, Router, UrlTree } from '@angular/router';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
@@ -28,6 +27,7 @@ import { SearchQueryResponse } from './search-query-response.model';
|
|||||||
import { SearchFilterConfig } from './search-filter-config.model';
|
import { SearchFilterConfig } from './search-filter-config.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
class DummyComponent {
|
class DummyComponent {
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import { Injectable, OnDestroy } from '@angular/core';
|
import { Injectable, OnDestroy } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute, NavigationExtras, Params, PRIMARY_OUTLET, Router,
|
ActivatedRoute,
|
||||||
|
NavigationExtras,
|
||||||
|
PRIMARY_OUTLET,
|
||||||
|
Router,
|
||||||
UrlSegmentGroup
|
UrlSegmentGroup
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { filter, flatMap, map, tap } from 'rxjs/operators';
|
import { flatMap, map } from 'rxjs/operators';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import {
|
import {
|
||||||
FacetConfigSuccessResponse,
|
FacetConfigSuccessResponse,
|
||||||
FacetValueSuccessResponse,
|
FacetValueSuccessResponse,
|
||||||
@@ -25,8 +26,7 @@ import { GenericConstructor } from '../../core/shared/generic-constructor';
|
|||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { configureRequest } from '../../core/shared/operators';
|
import { configureRequest } from '../../core/shared/operators';
|
||||||
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
import { URLCombiner } from '../../core/url-combiner/url-combiner';
|
||||||
import { hasNoValue, hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
import { NormalizedSearchResult } from '../normalized-search-result.model';
|
||||||
import { SearchOptions } from '../search-options.model';
|
import { SearchOptions } from '../search-options.model';
|
||||||
import { SearchResult } from '../search-result.model';
|
import { SearchResult } from '../search-result.model';
|
||||||
@@ -44,6 +44,7 @@ import { Community } from '../../core/shared/community.model';
|
|||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -13,6 +13,7 @@ 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 { hot } from 'jasmine-marbles';
|
||||||
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
|
|
||||||
describe('SearchSettingsComponent', () => {
|
describe('SearchSettingsComponent', () => {
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ describe('SearchSettingsComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||||
declarations: [SearchSettingsComponent, EnumKeysPipe],
|
declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: SearchService, useValue: searchServiceStub },
|
{ provide: SearchService, useValue: searchServiceStub },
|
||||||
|
|
||||||
@@ -96,15 +97,18 @@ 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', () => {
|
||||||
|
(comp as any).searchOptions$.first().subscribe((options) => {
|
||||||
|
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.query(By.css('.form-control')).children;
|
||||||
expect(childElements.length).toEqual(comp.searchOptionPossibilities.length);
|
expect(childElements.length).toEqual(comp.searchOptionPossibilities.length);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('it should show the size settings with the respective selectable options', () => {
|
it('it should show the size settings with the respective selectable options', () => {
|
||||||
(comp as any).filterService.getPaginatedSearchOptions().first().subscribe((options) => {
|
(comp as any).searchOptions$.first().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.query(By.css('.form-control')).children;
|
||||||
@@ -114,15 +118,21 @@ describe('SearchSettingsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have the proper order value selected by default', () => {
|
it('should have the proper order value selected by default', () => {
|
||||||
|
(comp as any).searchOptions$.first().subscribe((options) => {
|
||||||
|
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('.form-control option[value="0"][selected="selected"]'));
|
||||||
expect(childElementToBeSelected).toBeDefined();
|
expect(childElementToBeSelected).toBeDefined();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should have the proper rpp value selected by default', () => {
|
it('should have the proper rpp value selected by default', () => {
|
||||||
|
(comp as any).searchOptions$.first().subscribe((options) => {
|
||||||
|
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('.form-control option[value="10"][selected="selected"]'));
|
||||||
expect(childElementToBeSelected).toBeDefined();
|
expect(childElementToBeSelected).toBeDefined();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -18,7 +18,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|||||||
export class SearchSidebarComponent {
|
export class SearchSidebarComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The total amount of result
|
* The total amount of results
|
||||||
*/
|
*/
|
||||||
@Input() resultCount;
|
@Input() resultCount;
|
||||||
|
|
||||||
|
@@ -31,7 +31,7 @@ export class SearchSidebarService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the sidebar should currently be collapsed
|
* Checks if the sidebar should currently be collapsed
|
||||||
* @returns {Observable<boolean>} Emits true if our screen size is mobile or when the state in the store is currently collapsed
|
* @returns {Observable<boolean>} Emits true if the user's screen size is mobile or when the state in the store is currently collapsed
|
||||||
*/
|
*/
|
||||||
get isCollapsed(): Observable<boolean> {
|
get isCollapsed(): Observable<boolean> {
|
||||||
return Observable.combineLatest(
|
return Observable.combineLatest(
|
||||||
@@ -48,7 +48,7 @@ export class SearchSidebarService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatches a 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 SearchSidebarExpandAction());
|
||||||
|
8
src/app/core/shared/view-mode.model.ts
Normal file
8
src/app/core/shared/view-mode.model.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* This enumeration represents all possible ways of representing a group of objects in the UI
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum ViewMode {
|
||||||
|
List = 'list',
|
||||||
|
Grid = 'grid'
|
||||||
|
}
|
@@ -9,13 +9,6 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
import { hasValue, isNotEmpty } from '../empty.util';
|
import { hasValue, isNotEmpty } from '../empty.util';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component renders a simple item page.
|
|
||||||
* The route parameter 'id' is used to request the item it represents.
|
|
||||||
* All fields of the item that should be displayed, are defined in its template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-input-suggestions',
|
selector: 'ds-input-suggestions',
|
||||||
@@ -23,28 +16,92 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
templateUrl: './input-suggestions.component.html'
|
templateUrl: './input-suggestions.component.html'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing a form with a autocomplete functionality
|
||||||
|
*/
|
||||||
export class InputSuggestionsComponent {
|
export class InputSuggestionsComponent {
|
||||||
|
/**
|
||||||
|
* The suggestions that should be shown
|
||||||
|
*/
|
||||||
@Input() suggestions: any[] = [];
|
@Input() suggestions: any[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The time waited to detect if any other input will follow before requesting the suggestions
|
||||||
|
*/
|
||||||
@Input() debounceTime = 500;
|
@Input() debounceTime = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder attribute for the input field
|
||||||
|
*/
|
||||||
@Input() placeholder = '';
|
@Input() placeholder = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action attribute for the form
|
||||||
|
*/
|
||||||
@Input() action;
|
@Input() action;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name attribute for the input field
|
||||||
|
*/
|
||||||
@Input() name;
|
@Input() name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value of the input field
|
||||||
|
*/
|
||||||
@Input() ngModel;
|
@Input() ngModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output for when the input field's value changes
|
||||||
|
*/
|
||||||
@Output() ngModelChange = new EventEmitter();
|
@Output() ngModelChange = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output for when the form is submitted
|
||||||
|
*/
|
||||||
@Output() submitSuggestion = new EventEmitter();
|
@Output() submitSuggestion = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output for when a suggestion is clicked
|
||||||
|
*/
|
||||||
@Output() clickSuggestion = new EventEmitter();
|
@Output() clickSuggestion = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output for when new suggestions should be requested
|
||||||
|
*/
|
||||||
@Output() findSuggestions = new EventEmitter();
|
@Output() findSuggestions = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits true when the list of suggestions should be shown
|
||||||
|
*/
|
||||||
show = new BehaviorSubject<boolean>(false);
|
show = new BehaviorSubject<boolean>(false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index of the currently selected suggestion
|
||||||
|
*/
|
||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the input field component
|
||||||
|
*/
|
||||||
@ViewChild('inputField') queryInput: ElementRef;
|
@ViewChild('inputField') queryInput: ElementRef;
|
||||||
|
/**
|
||||||
|
* Reference to the suggestion components
|
||||||
|
*/
|
||||||
@ViewChildren('suggestion') resultViews: QueryList<ElementRef>;
|
@ViewChildren('suggestion') resultViews: QueryList<ElementRef>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When any of the inputs change, check if we should still show the suggestions
|
||||||
|
*/
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (hasValue(changes.suggestions)) {
|
if (hasValue(changes.suggestions)) {
|
||||||
this.show.next(isNotEmpty(changes.suggestions.currentValue));
|
this.show.next(isNotEmpty(changes.suggestions.currentValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the focus on one of the suggestions up to the previous suggestion
|
||||||
|
* When no suggestion is currently in focus OR the first suggestion is in focus: shift to the last suggestion
|
||||||
|
*/
|
||||||
shiftFocusUp(event: KeyboardEvent) {
|
shiftFocusUp(event: KeyboardEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.selectedIndex > 0) {
|
if (this.selectedIndex > 0) {
|
||||||
@@ -56,6 +113,10 @@ export class InputSuggestionsComponent {
|
|||||||
this.changeFocus();
|
this.changeFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the focus on one of the suggestions up to the next suggestion
|
||||||
|
* When no suggestion is currently in focus OR the last suggestion is in focus: shift to the first suggestion
|
||||||
|
*/
|
||||||
shiftFocusDown(event: KeyboardEvent) {
|
shiftFocusDown(event: KeyboardEvent) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.selectedIndex >= 0) {
|
if (this.selectedIndex >= 0) {
|
||||||
@@ -67,26 +128,42 @@ export class InputSuggestionsComponent {
|
|||||||
this.changeFocus();
|
this.changeFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the change of focus to the current selectedIndex
|
||||||
|
*/
|
||||||
changeFocus() {
|
changeFocus() {
|
||||||
if (this.resultViews.length > 0) {
|
if (this.resultViews.length > 0) {
|
||||||
this.resultViews.toArray()[this.selectedIndex].nativeElement.focus();
|
this.resultViews.toArray()[this.selectedIndex].nativeElement.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When any key is pressed (except for the Enter button) the query input should move to the input field
|
||||||
|
* @param {KeyboardEvent} event The keyboard event
|
||||||
|
*/
|
||||||
onKeydown(event: KeyboardEvent) {
|
onKeydown(event: KeyboardEvent) {
|
||||||
if (event.key !== 'Enter') {
|
if (event.key !== 'Enter') {
|
||||||
this.queryInput.nativeElement.focus();
|
this.queryInput.nativeElement.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the show variable so the suggestion dropdown closes
|
||||||
|
*/
|
||||||
close() {
|
close() {
|
||||||
this.show.next(false);
|
this.show.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For usage of the isNotEmpty function in the template
|
||||||
|
*/
|
||||||
isNotEmpty(data) {
|
isNotEmpty(data) {
|
||||||
return isNotEmpty(data);
|
return isNotEmpty(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that if a suggestion is clicked, the suggestions dropdown closes and the focus moves to the input field
|
||||||
|
*/
|
||||||
onClickSuggestion(data) {
|
onClickSuggestion(data) {
|
||||||
this.clickSuggestion.emit(data);
|
this.clickSuggestion.emit(data);
|
||||||
this.close();
|
this.close();
|
||||||
|
@@ -1,13 +1,11 @@
|
|||||||
import { ObjectCollectionComponent } from './object-collection.component';
|
import { ObjectCollectionComponent } from './object-collection.component';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { element } from 'protractor';
|
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { Config } from '../../../config/config.interface';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { RouterStub } from '../testing/router-stub';
|
import { RouterStub } from '../testing/router-stub';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
describe('ObjectCollectionComponent', () => {
|
describe('ObjectCollectionComponent', () => {
|
||||||
let fixture: ComponentFixture<ObjectCollectionComponent>;
|
let fixture: ComponentFixture<ObjectCollectionComponent>;
|
||||||
|
@@ -14,8 +14,8 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
|
|||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
|
||||||
import { ListableObject } from './shared/listable-object.model';
|
import { ListableObject } from './shared/listable-object.model';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { hasValue, isNotEmpty } from '../empty.util';
|
import { hasValue, isNotEmpty } from '../empty.util';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-viewable-collection',
|
selector: 'ds-viewable-collection',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
|
||||||
import { renderElementsFor } from './dso-element-decorator';
|
import { renderElementsFor } from './dso-element-decorator';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
describe('ElementDecorator', () => {
|
describe('ElementDecorator', () => {
|
||||||
const gridDecorator = renderElementsFor(Item, ViewMode.Grid);
|
const gridDecorator = renderElementsFor(Item, ViewMode.Grid);
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
import { ListableObject } from './listable-object.model';
|
import { ListableObject } from './listable-object.model';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
const dsoElementMap = new Map();
|
const dsoElementMap = new Map();
|
||||||
export function renderElementsFor(listable: GenericConstructor<ListableObject>, viewMode: ViewMode) {
|
export function renderElementsFor(listable: GenericConstructor<ListableObject>, viewMode: ViewMode) {
|
||||||
|
@@ -2,8 +2,8 @@ import { Component, Inject } from '@angular/core';
|
|||||||
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-grid-element',
|
selector: 'ds-collection-grid-element',
|
||||||
|
@@ -3,7 +3,7 @@ import { Component, Input, Inject } from '@angular/core';
|
|||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-grid-element',
|
selector: 'ds-community-grid-element',
|
||||||
|
@@ -3,7 +3,7 @@ import { Component, Input, Inject } from '@angular/core';
|
|||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor} from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-grid-element',
|
selector: 'ds-item-grid-element',
|
||||||
|
@@ -3,8 +3,8 @@ import { Component } from '@angular/core';
|
|||||||
import { renderElementsFor} from '../../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor} from '../../../object-collection/shared/dso-element-decorator';
|
||||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-search-result-grid-element',
|
selector: 'ds-collection-search-result-grid-element',
|
||||||
|
@@ -2,8 +2,8 @@ import { Component } from '@angular/core';
|
|||||||
import { Community } from '../../../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-search-result-grid-element',
|
selector: 'ds-community-search-result-grid-element',
|
||||||
|
@@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element
|
|||||||
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
import { SearchResultGridElementComponent } from '../search-result-grid-element.component';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { focusShadow } from '../../../../shared/animations/focus';
|
import { focusShadow } from '../../../../shared/animations/focus';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-search-result-grid-element',
|
selector: 'ds-item-search-result-grid-element',
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { Component, Injector, Input, OnInit } from '@angular/core';
|
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
|
||||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator';
|
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-wrapper-grid-element',
|
selector: 'ds-wrapper-grid-element',
|
||||||
|
@@ -2,8 +2,8 @@ import { Component, Inject } from '@angular/core';
|
|||||||
|
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-list-element',
|
selector: 'ds-collection-list-element',
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Input, Inject } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-list-element',
|
selector: 'ds-community-list-element',
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Input, Inject } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor } from '../../object-collection/shared/dso-element-decorator';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-list-element',
|
selector: 'ds-item-list-element',
|
||||||
|
@@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element
|
|||||||
|
|
||||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
import { CollectionSearchResult } from '../../../object-collection/shared/collection-search-result.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-search-result-list-element',
|
selector: 'ds-collection-search-result-list-element',
|
||||||
|
@@ -4,8 +4,8 @@ import { renderElementsFor } from '../../../object-collection/shared/dso-element
|
|||||||
|
|
||||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||||
import { Community } from '../../../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
import { CommunitySearchResult } from '../../../object-collection/shared/community-search-result.model';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-search-result-list-element',
|
selector: 'ds-community-search-result-list-element',
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
import { renderElementsFor } from '../../../object-collection/shared/dso-element-decorator';
|
||||||
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
import { SearchResultListElementComponent } from '../search-result-list-element.component';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
|
||||||
import { ViewMode } from '../../../../+search-page/search-options.model';
|
|
||||||
import { ListableObject } from '../../../object-collection/shared/listable-object.model';
|
|
||||||
import { focusBackground } from '../../../animations/focus';
|
import { focusBackground } from '../../../animations/focus';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-search-result-list-element',
|
selector: 'ds-item-search-result-list-element',
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
import { Component, Injector, Input, OnInit } from '@angular/core';
|
import { Component, Injector, Input, OnInit } from '@angular/core';
|
||||||
import { ViewMode } from '../../../+search-page/search-options.model';
|
|
||||||
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'
|
import { rendersDSOType } from '../../object-collection/shared/dso-element-decorator'
|
||||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-wrapper-list-element',
|
selector: 'ds-wrapper-list-element',
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/search">
|
<form #form="ngForm" (ngSubmit)="onSubmit(form.value)" class="row" action="/search">
|
||||||
<div *ngIf="isNotEmpty(scopes)" class="col-12 col-sm-3">
|
<div *ngIf="isNotEmpty(scopes)" class="col-12 col-sm-3">
|
||||||
<select [(ngModel)]="selectedId" name="scope" class="form-control" aria-label="Search scope" (change)="onScopeChange($event.target.value)">
|
<select [(ngModel)]="scope" name="scope" class="form-control" aria-label="Search scope" (change)="onScopeChange($event.target.value)">
|
||||||
<option value>{{'search.form.search_dspace' | translate}}</option>
|
<option value>{{'search.form.search_dspace' | translate}}</option>
|
||||||
<option *ngFor="let scopeOption of scopes" [value]="scopeOption.id">{{scopeOption?.name ? scopeOption.name : 'search.form.search_dspace' | translate}}</option>
|
<option *ngFor="let scopeOption of scopes" [value]="scopeOption.id">{{scopeOption?.name ? scopeOption.name : 'search.form.search_dspace' | translate}}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@@ -3,6 +3,7 @@ import { SearchService } from '../../+search-page/search-service/search.service'
|
|||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util';
|
import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util';
|
||||||
|
import { QueryParamsHandling } from '@angular/router/src/config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -15,36 +16,66 @@ import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util';
|
|||||||
styleUrls: ['./search-form.component.scss'],
|
styleUrls: ['./search-form.component.scss'],
|
||||||
templateUrl: './search-form.component.html'
|
templateUrl: './search-form.component.html'
|
||||||
})
|
})
|
||||||
export class SearchFormComponent {
|
|
||||||
@Input() query: string;
|
|
||||||
selectedId = '';
|
|
||||||
@Input() currentUrl: string;
|
|
||||||
@Input() scopes: DSpaceObject[];
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that represents the search form
|
||||||
|
*/
|
||||||
|
export class SearchFormComponent {
|
||||||
|
/**
|
||||||
|
* The search query
|
||||||
|
*/
|
||||||
|
@Input() query: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected scope object's UUID
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
set scope(id: string) {
|
scope = '';
|
||||||
this.selectedId = id;
|
@Input() currentUrl: string;
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* The available scopes
|
||||||
|
*/
|
||||||
|
@Input() scopes: DSpaceObject[];
|
||||||
|
|
||||||
constructor(private router: Router) {
|
constructor(private router: Router) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the search when the form is submitted
|
||||||
|
* @param data Values submitted using the form
|
||||||
|
*/
|
||||||
onSubmit(data: any) {
|
onSubmit(data: any) {
|
||||||
this.updateSearch(data);
|
this.updateSearch(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the search when the current scope has been changed
|
||||||
|
* @param {string} scope The new scope
|
||||||
|
*/
|
||||||
onScopeChange(scope: string) {
|
onScopeChange(scope: string) {
|
||||||
this.updateSearch({ scope });
|
this.updateSearch({ scope });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the search URL
|
||||||
|
* @param data Updated parameters
|
||||||
|
*/
|
||||||
updateSearch(data: any) {
|
updateSearch(data: any) {
|
||||||
const newUrl = hasValue(this.currentUrl) ? this.currentUrl : 'search';
|
const newUrl = hasValue(this.currentUrl) ? this.currentUrl : '/search';
|
||||||
|
let handling: QueryParamsHandling = '' ;
|
||||||
|
if (this.currentUrl === '/search') {
|
||||||
|
handling = 'merge';
|
||||||
|
}
|
||||||
this.router.navigate([newUrl], {
|
this.router.navigate([newUrl], {
|
||||||
queryParams: Object.assign({}, { page: 1 }, data),
|
queryParams: Object.assign({}, { page: 1 }, data),
|
||||||
queryParamsHandling: 'merge'
|
queryParamsHandling: handling
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For usage of the isNotEmpty function in the template
|
||||||
|
*/
|
||||||
isNotEmpty(object: any) {
|
isNotEmpty(object: any) {
|
||||||
return isNotEmpty(object);
|
return isNotEmpty(object);
|
||||||
}
|
}
|
||||||
|
@@ -156,6 +156,10 @@ const DIRECTIVES = [
|
|||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components and pipes that need to be shared among multiple other modules
|
||||||
|
*/
|
||||||
export class SharedModule {
|
export class SharedModule {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
|
||||||
|
|
||||||
export class HALEndpointServiceStub {
|
export class HALEndpointServiceStub {
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
export class SearchServiceStub {
|
export class SearchServiceStub {
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { TruncatableService } from '../truncatable.service';
|
import { TruncatableService } from '../truncatable.service';
|
||||||
|
import { hasValue } from '../../empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-truncatable-part',
|
selector: 'ds-truncatable-part',
|
||||||
@@ -7,22 +8,59 @@ import { TruncatableService } from '../truncatable.service';
|
|||||||
styleUrls: ['./truncatable-part.component.scss']
|
styleUrls: ['./truncatable-part.component.scss']
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that truncates/clamps a piece of text
|
||||||
|
* It needs a TruncatableComponent parent to identify it's current state
|
||||||
|
*/
|
||||||
export class TruncatablePartComponent implements OnInit, OnDestroy {
|
export class TruncatablePartComponent implements OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* Number of lines shown when the part is collapsed
|
||||||
|
*/
|
||||||
@Input() minLines: number;
|
@Input() minLines: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of lines shown when the part is expanded. -1 indicates no limit
|
||||||
|
*/
|
||||||
@Input() maxLines = -1;
|
@Input() maxLines = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier of the parent TruncatableComponent
|
||||||
|
*/
|
||||||
@Input() id: string;
|
@Input() id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of text, can be a h4 for headers or any other class you want to add
|
||||||
|
*/
|
||||||
@Input() type: string;
|
@Input() type: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the minimal height of the part should at least be as high as it's minimum amount of lines
|
||||||
|
*/
|
||||||
@Input() fixedHeight = false;
|
@Input() fixedHeight = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current amount of lines shown of this part
|
||||||
|
*/
|
||||||
lines: string;
|
lines: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to unsubscribe from
|
||||||
|
*/
|
||||||
private sub;
|
private sub;
|
||||||
|
|
||||||
public constructor(private service: TruncatableService) {
|
public constructor(private service: TruncatableService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize lines variable
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.setLines();
|
this.setLines();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to the current state to determine how much lines should be shown of this part
|
||||||
|
*/
|
||||||
private setLines() {
|
private setLines() {
|
||||||
this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => {
|
this.sub = this.service.isCollapsed(this.id).subscribe((collapsed: boolean) => {
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
@@ -33,7 +71,12 @@ export class TruncatablePartComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from the subscription
|
||||||
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.sub)) {
|
||||||
this.sub.unsubscribe();
|
this.sub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,22 +16,43 @@ export const TruncatableActionTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class TruncatableAction implements Action {
|
export class TruncatableAction implements Action {
|
||||||
|
/**
|
||||||
|
* UUID of the truncatable component the action is performed on, used to identify the filter
|
||||||
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of action that will be performed
|
||||||
|
*/
|
||||||
type;
|
type;
|
||||||
constructor(name: string) {
|
|
||||||
this.id = name;
|
/**
|
||||||
|
* Initialize with the truncatable component's UUID
|
||||||
|
* @param {string} id of the filter
|
||||||
|
*/
|
||||||
|
constructor(id: string) {
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
/**
|
||||||
|
* Used to collapse a truncatable component when it's expanded and expand it when it's collapsed
|
||||||
|
*/
|
||||||
export class TruncatableToggleAction extends TruncatableAction {
|
export class TruncatableToggleAction extends TruncatableAction {
|
||||||
type = TruncatableActionTypes.TOGGLE;
|
type = TruncatableActionTypes.TOGGLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to collapse a truncatable component
|
||||||
|
*/
|
||||||
export class TruncatableCollapseAction extends TruncatableAction {
|
export class TruncatableCollapseAction extends TruncatableAction {
|
||||||
type = TruncatableActionTypes.COLLAPSE;
|
type = TruncatableActionTypes.COLLAPSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to expand a truncatable component
|
||||||
|
*/
|
||||||
export class TruncatableExpandAction extends TruncatableAction {
|
export class TruncatableExpandAction extends TruncatableAction {
|
||||||
type = TruncatableActionTypes.EXPAND;
|
type = TruncatableActionTypes.EXPAND;
|
||||||
}
|
}
|
||||||
|
@@ -9,14 +9,32 @@ import { TruncatableService } from './truncatable.service';
|
|||||||
styleUrls: ['./truncatable.component.scss'],
|
styleUrls: ['./truncatable.component.scss'],
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that represents a section with one or more truncatable parts that all listen to this state
|
||||||
|
*/
|
||||||
export class TruncatableComponent {
|
export class TruncatableComponent {
|
||||||
|
/**
|
||||||
|
* Is true when all truncatable parts in this truncatable should be expanded on loading
|
||||||
|
*/
|
||||||
@Input() initialExpand = false;
|
@Input() initialExpand = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique identifier of this truncatable component
|
||||||
|
*/
|
||||||
@Input() id: string;
|
@Input() id: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is true when the truncatable should expand on both hover as click
|
||||||
|
*/
|
||||||
@Input() onHover = false;
|
@Input() onHover = false;
|
||||||
|
|
||||||
public constructor(private service: TruncatableService) {
|
public constructor(private service: TruncatableService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the initial state
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.initialExpand) {
|
if (this.initialExpand) {
|
||||||
this.service.expand(this.id);
|
this.service.expand(this.id);
|
||||||
@@ -25,18 +43,27 @@ export class TruncatableComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If onHover is true, collapses the truncatable
|
||||||
|
*/
|
||||||
public hoverCollapse() {
|
public hoverCollapse() {
|
||||||
if (this.onHover) {
|
if (this.onHover) {
|
||||||
this.service.collapse(this.id);
|
this.service.collapse(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If onHover is true, expands the truncatable
|
||||||
|
*/
|
||||||
public hoverExpand() {
|
public hoverExpand() {
|
||||||
if (this.onHover) {
|
if (this.onHover) {
|
||||||
this.service.expand(this.id);
|
this.service.expand(this.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands the truncatable when it's collapsed, collapses it when it's expanded
|
||||||
|
*/
|
||||||
public toggle() {
|
public toggle() {
|
||||||
this.service.toggle(this.id);
|
this.service.toggle(this.id);
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,27 @@
|
|||||||
import { TruncatableAction, TruncatableActionTypes } from './truncatable.actions';
|
import { TruncatableAction, TruncatableActionTypes } from './truncatable.actions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that represents the state of a single truncatable
|
||||||
|
*/
|
||||||
export interface TruncatableState {
|
export interface TruncatableState {
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that represents the state of all truncatable
|
||||||
|
*/
|
||||||
export interface TruncatablesState {
|
export interface TruncatablesState {
|
||||||
[id: string]: TruncatableState
|
[id: string]: TruncatableState
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: TruncatablesState = Object.create(null);
|
const initialState: TruncatablesState = Object.create(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a truncatable action on the current state
|
||||||
|
* @param {TruncatablesState} state The state before the action is performed
|
||||||
|
* @param {TruncatableAction} action The action that should be performed
|
||||||
|
* @returns {TruncatablesState} The state after the action is performed
|
||||||
|
*/
|
||||||
export function truncatableReducer(state = initialState, action: TruncatableAction): TruncatablesState {
|
export function truncatableReducer(state = initialState, action: TruncatableAction): TruncatablesState {
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
@@ -7,12 +7,20 @@ import { hasValue } from '../empty.util';
|
|||||||
|
|
||||||
const truncatableStateSelector = (state: TruncatablesState) => state.truncatable;
|
const truncatableStateSelector = (state: TruncatablesState) => state.truncatable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service responsible for truncating/clamping text and performing actions on truncatable elements
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TruncatableService {
|
export class TruncatableService {
|
||||||
|
|
||||||
constructor(private store: Store<TruncatablesState>) {
|
constructor(private store: Store<TruncatablesState>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a trunctable component should currently be collapsed
|
||||||
|
* @param {string} id The UUID of the truncatable component
|
||||||
|
* @returns {Observable<boolean>} Emits true if the state in the store is currently collapsed for the given truncatable component
|
||||||
|
*/
|
||||||
isCollapsed(id: string): Observable<boolean> {
|
isCollapsed(id: string): Observable<boolean> {
|
||||||
return this.store.select(truncatableByIdSelector(id))
|
return this.store.select(truncatableByIdSelector(id))
|
||||||
.map((object: TruncatableState) => {
|
.map((object: TruncatableState) => {
|
||||||
@@ -24,14 +32,26 @@ export class TruncatableService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a toggle action to the store for a given truncatable component
|
||||||
|
* @param {string} id The identifier of the truncatable for which the action is dispatched
|
||||||
|
*/
|
||||||
public toggle(id: string): void {
|
public toggle(id: string): void {
|
||||||
this.store.dispatch(new TruncatableToggleAction(id));
|
this.store.dispatch(new TruncatableToggleAction(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches a collapse action to the store for a given truncatable component
|
||||||
|
* @param {string} id The identifier of the truncatable for which the action is dispatched
|
||||||
|
*/
|
||||||
public collapse(id: string): void {
|
public collapse(id: string): void {
|
||||||
this.store.dispatch(new TruncatableCollapseAction(id));
|
this.store.dispatch(new TruncatableCollapseAction(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches an expand action to the store for a given truncatable component
|
||||||
|
* @param {string} id The identifier of the truncatable for which the action is dispatched
|
||||||
|
*/
|
||||||
public expand(id: string): void {
|
public expand(id: string): void {
|
||||||
this.store.dispatch(new TruncatableExpandAction(id));
|
this.store.dispatch(new TruncatableExpandAction(id));
|
||||||
}
|
}
|
||||||
|
@@ -1,14 +1,18 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
|
|
||||||
/**
|
|
||||||
* Pipe to truncate a value in Angular. (Take a substring, starting at 0)
|
|
||||||
* Default value: 10
|
|
||||||
*/
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'dsCapitalize'
|
name: 'dsCapitalize'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe for capizalizing a string
|
||||||
|
*/
|
||||||
export class CapitalizePipe implements PipeTransform {
|
export class CapitalizePipe implements PipeTransform {
|
||||||
transform(value: string, args: string[]): string {
|
/**
|
||||||
|
* @param {string} value String to be capitalized
|
||||||
|
* @returns {string} Capitalized version of the input value
|
||||||
|
*/
|
||||||
|
transform(value: string): string {
|
||||||
if (value) {
|
if (value) {
|
||||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,15 @@
|
|||||||
import {Directive, ElementRef, Output, EventEmitter, HostListener} from '@angular/core';
|
import { Directive, ElementRef, Output, EventEmitter, HostListener } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
@Directive({
|
||||||
selector: '[dsClickOutside]'
|
selector: '[dsClickOutside]'
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* Directive to detect when the users clicks outside of the element the directive was put on
|
||||||
|
*/
|
||||||
export class ClickOutsideDirective {
|
export class ClickOutsideDirective {
|
||||||
|
/**
|
||||||
|
* Emits null when the user clicks outside of the element
|
||||||
|
*/
|
||||||
@Output()
|
@Output()
|
||||||
public dsClickOutside = new EventEmitter();
|
public dsClickOutside = new EventEmitter();
|
||||||
|
|
||||||
|
@@ -8,22 +8,45 @@ import { Subject } from 'rxjs/Subject';
|
|||||||
@Directive({
|
@Directive({
|
||||||
selector: '[ngModel][dsDebounce]',
|
selector: '[ngModel][dsDebounce]',
|
||||||
})
|
})
|
||||||
|
/**
|
||||||
|
* Directive for setting a debounce time on an input field
|
||||||
|
* It will emit the input field's value when no changes were made to this value in a given debounce time
|
||||||
|
*/
|
||||||
export class DebounceDirective implements OnInit, OnDestroy {
|
export class DebounceDirective implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a value when nothing has changed in dsDebounce milliseconds
|
||||||
|
*/
|
||||||
@Output()
|
@Output()
|
||||||
public onDebounce = new EventEmitter<any>();
|
public onDebounce = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debounce time in milliseconds
|
||||||
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
public dsDebounce = 500;
|
public dsDebounce = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if no changes have been made to the input field's value
|
||||||
|
*/
|
||||||
private isFirstChange = true;
|
private isFirstChange = true;
|
||||||
private ngUnsubscribe: Subject<void> = new Subject<void>();
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subject to unsubscribe from
|
||||||
|
*/
|
||||||
|
private subject: Subject<void> = new Subject<void>();
|
||||||
|
|
||||||
constructor(public model: NgControl) {
|
constructor(public model: NgControl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start listening to changes of the input field's value changes
|
||||||
|
* Emit it when the debounceTime is over without new changes
|
||||||
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.model.valueChanges
|
this.model.valueChanges
|
||||||
.takeUntil(this.ngUnsubscribe)
|
.takeUntil(this.subject)
|
||||||
.debounceTime(this.dsDebounce)
|
.debounceTime(this.dsDebounce)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.subscribe((modelValue) => {
|
.subscribe((modelValue) => {
|
||||||
@@ -35,8 +58,11 @@ export class DebounceDirective implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close subject
|
||||||
|
*/
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.ngUnsubscribe.next();
|
this.subject.next();
|
||||||
this.ngUnsubscribe.complete();
|
this.subject.complete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,15 +3,36 @@ import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
|
|||||||
@Directive({
|
@Directive({
|
||||||
selector: '[dsDragClick]'
|
selector: '[dsDragClick]'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive for preventing drag events being misinterpret as clicks
|
||||||
|
* The difference is made using the time the mouse button is pushed down
|
||||||
|
*/
|
||||||
export class DragClickDirective {
|
export class DragClickDirective {
|
||||||
|
/**
|
||||||
|
* The start time of the mouse down event in milliseconds
|
||||||
|
*/
|
||||||
private start;
|
private start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits a click event when the click is perceived as an actual click and not a drag
|
||||||
|
*/
|
||||||
@Output() actualClick = new EventEmitter();
|
@Output() actualClick = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the mouse button is pushed down, register the start time
|
||||||
|
* @param event Mouse down event
|
||||||
|
*/
|
||||||
@HostListener('mousedown', ['$event'])
|
@HostListener('mousedown', ['$event'])
|
||||||
mousedownEvent(event) {
|
mousedownEvent(event) {
|
||||||
this.start = new Date();
|
this.start = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the mouse button is let go of, check how long if it was down for
|
||||||
|
* If the mouse button was down for more than 250ms, don't emit a click event
|
||||||
|
* @param event Mouse down event
|
||||||
|
*/
|
||||||
@HostListener('mouseup', ['$event'])
|
@HostListener('mouseup', ['$event'])
|
||||||
mouseupEvent(event) {
|
mouseupEvent(event) {
|
||||||
const end: any = new Date();
|
const end: any = new Date();
|
||||||
|
@@ -1,7 +1,13 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
@Pipe({ name: 'dsEmphasize' })
|
@Pipe({ name: 'dsEmphasize' })
|
||||||
|
/**
|
||||||
|
* Pipe for emphasizing a part of a string by surrounding it with <em> tags
|
||||||
|
*/
|
||||||
export class EmphasizePipe implements PipeTransform {
|
export class EmphasizePipe implements PipeTransform {
|
||||||
|
/**
|
||||||
|
* Characters that should be escaped
|
||||||
|
*/
|
||||||
specials = [
|
specials = [
|
||||||
// order matters for these
|
// order matters for these
|
||||||
'-'
|
'-'
|
||||||
@@ -22,14 +28,28 @@ export class EmphasizePipe implements PipeTransform {
|
|||||||
, '$'
|
, '$'
|
||||||
, '|'
|
, '|'
|
||||||
];
|
];
|
||||||
|
/**
|
||||||
|
* Regular expression for escaping the string we're trying to find
|
||||||
|
*/
|
||||||
regex = RegExp('[' + this.specials.join('\\') + ']', 'g');
|
regex = RegExp('[' + this.specials.join('\\') + ']', 'g');
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param haystack The string which we want to partly highlight
|
||||||
|
* @param needle The string that should become emphasized in the haystack string
|
||||||
|
* @returns {any} Transformed haystack with the needle emphasized
|
||||||
|
*/
|
||||||
transform(haystack, needle): any {
|
transform(haystack, needle): any {
|
||||||
const escaped = this.escapeRegExp(needle);
|
const escaped = this.escapeRegExp(needle);
|
||||||
const reg = new RegExp(escaped, 'gi');
|
const reg = new RegExp(escaped, 'gi');
|
||||||
return haystack.replace(reg, '<em>$&</em>');
|
return haystack.replace(reg, '<em>$&</em>');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param str Escape special characters in the string we're looking for
|
||||||
|
* @returns {any} The escaped version of the input string
|
||||||
|
*/
|
||||||
escapeRegExp(str) {
|
escapeRegExp(str) {
|
||||||
return str.replace(this.regex, '\\$&');
|
return str.replace(this.regex, '\\$&');
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,16 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
@Pipe({ name: 'dsKeys' })
|
@Pipe({ name: 'dsKeys' })
|
||||||
|
/**
|
||||||
|
* Pipe for parsing all values of an enumeration to an array of key-value pairs
|
||||||
|
*/
|
||||||
export class EnumKeysPipe implements PipeTransform {
|
export class EnumKeysPipe implements PipeTransform {
|
||||||
transform(value, args: string[]): any {
|
|
||||||
|
/**
|
||||||
|
* @param value An enumeration
|
||||||
|
* @returns {any} Array with all keys and values of the input enumeration
|
||||||
|
*/
|
||||||
|
transform(value): any {
|
||||||
const keys = [];
|
const keys = [];
|
||||||
for (const enumMember in value) {
|
for (const enumMember in value) {
|
||||||
if (!isNaN(parseInt(enumMember, 10))) {
|
if (!isNaN(parseInt(enumMember, 10))) {
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
import { PipeTransform, Pipe } from '@angular/core';
|
import { PipeTransform, Pipe } from '@angular/core';
|
||||||
|
|
||||||
@Pipe({name: 'dsObjectKeys'})
|
@Pipe({name: 'dsObjectKeys'})
|
||||||
|
/**
|
||||||
|
* Pipe for parsing all keys of an object to an array of key-value pairs
|
||||||
|
*/
|
||||||
export class ObjectKeysPipe implements PipeTransform {
|
export class ObjectKeysPipe implements PipeTransform {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value An object
|
||||||
|
* @returns {any} Array with all keys the input object
|
||||||
|
*/
|
||||||
transform(value, args:string[]): any {
|
transform(value, args:string[]): any {
|
||||||
const keys = [];
|
const keys = [];
|
||||||
Object.keys(value).forEach((k) => keys.push(k));
|
Object.keys(value).forEach((k) => keys.push(k));
|
||||||
|
@@ -8,8 +8,8 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|||||||
|
|
||||||
import { SearchService } from '../../+search-page/search-service/search.service';
|
import { SearchService } from '../../+search-page/search-service/search.service';
|
||||||
import { ViewModeSwitchComponent } from './view-mode-switch.component';
|
import { ViewModeSwitchComponent } from './view-mode-switch.component';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { SearchServiceStub } from '../testing/search-service-stub';
|
import { SearchServiceStub } from '../testing/search-service-stub';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({ template: '' })
|
@Component({ template: '' })
|
||||||
class DummyComponent { }
|
class DummyComponent { }
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ViewMode } from '../../+search-page/search-options.model';
|
|
||||||
import { SearchService } from './../../+search-page/search-service/search.service';
|
import { SearchService } from './../../+search-page/search-service/search.service';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to switch between list and grid views.
|
* Component to switch between list and grid views.
|
||||||
|
Reference in New Issue
Block a user