From c125f36f43403c320258bb0ef4571ee3500770bc Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 20 Apr 2018 11:17:52 +0200 Subject: [PATCH 01/13] fixed an issue where browse definition REST URLs would be wrong in certain cases --- src/app/core/browse/browse.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts index a7b7314d54..2e99dcc0d3 100644 --- a/src/app/core/browse/browse.service.ts +++ b/src/app/core/browse/browse.service.ts @@ -36,7 +36,7 @@ export class BrowseService { getBrowseURLFor(metadatumKey: string, linkPath: string): Observable { const searchKeyArray = BrowseService.toSearchKeyArray(metadatumKey); - return this.halService.getEndpoint(linkPath) + return this.halService.getEndpoint(this.linkPath) .filter((href: string) => isNotEmpty(href)) .distinctUntilChanged() .map((endpointURL: string) => new BrowseEndpointRequest(this.requestService.generateRequestId(), endpointURL)) From 2317f1f3a30806950e8e26dbd963fe5e030c530c Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 20 Apr 2018 14:19:18 +0200 Subject: [PATCH 02/13] fixed an issue where changing the sort direction wouldn't work --- resources/i18n/en.json | 4 ++++ .../search-filter/search-filter.service.ts | 6 ++++-- .../search-settings/search-settings.component.html | 2 +- src/app/core/cache/models/sort-options.model.ts | 6 +++--- src/app/shared/pagination/pagination.component.html | 2 +- src/app/shared/pagination/pagination.component.ts | 2 +- src/app/shared/route.service.ts | 10 +++++----- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 5c750b2397..53ae9015f6 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -56,6 +56,10 @@ "detail": "{{ range }} of {{ total }}" } }, + "sorting": { + "ASC": "Ascending", + "DESC": "Descending" + }, "title": "DSpace", "404": { "help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 1ba8182d33..62f906c004 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -10,7 +10,7 @@ import { SearchFilterInitialExpandAction, SearchFilterResetPageAction, SearchFilterToggleAction } from './search-filter.actions'; -import { hasValue, } from '../../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty, } from '../../../shared/empty.util'; import { SearchFilterConfig } from '../../search-service/search-filter-config.model'; import { SearchService } from '../../search-service/search.service'; import { RouteService } from '../../../shared/route.service'; @@ -59,7 +59,9 @@ export class SearchFilterService { getCurrentSort(): Observable { const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); const sortField$ = this.routeService.getQueryParameterValue('sortField'); - return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => new SortOptions(sortField || undefined, SortDirection[sortDirection])); + return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => + new SortOptions(isNotEmpty(sortField) ? sortField : undefined, SortDirection[sortDirection]) + ); } getCurrentFilters() { diff --git a/src/app/+search-page/search-settings/search-settings.component.html b/src/app/+search-page/search-settings/search-settings.component.html index b0d3294e30..18fd45caed 100644 --- a/src/app/+search-page/search-settings/search-settings.component.html +++ b/src/app/+search-page/search-settings/search-settings.component.html @@ -5,7 +5,7 @@ diff --git a/src/app/core/cache/models/sort-options.model.ts b/src/app/core/cache/models/sort-options.model.ts index 751b72b399..b2380cbaf3 100644 --- a/src/app/core/cache/models/sort-options.model.ts +++ b/src/app/core/cache/models/sort-options.model.ts @@ -1,10 +1,10 @@ export enum SortDirection { - Ascending = 'ASC', - Descending = 'DESC' + ASC = 'ASC', + DESC = 'DESC' } export class SortOptions { - constructor(public field: string = 'dc.title', public direction: SortDirection = SortDirection.Ascending) { + constructor(public field: string = 'dc.title', public direction: SortDirection = SortDirection.ASC) { } } diff --git a/src/app/shared/pagination/pagination.component.html b/src/app/shared/pagination/pagination.component.html index 0ad812a6b6..e974bb6eb0 100644 --- a/src/app/shared/pagination/pagination.component.html +++ b/src/app/shared/pagination/pagination.component.html @@ -12,7 +12,7 @@ - + diff --git a/src/app/shared/pagination/pagination.component.ts b/src/app/shared/pagination/pagination.component.ts index 6ab0e2a567..faaf20ec79 100644 --- a/src/app/shared/pagination/pagination.component.ts +++ b/src/app/shared/pagination/pagination.component.ts @@ -144,7 +144,7 @@ export class PaginationComponent implements OnDestroy, OnInit { /** * Direction in which to sort: ascending or descending */ - public sortDirection: SortDirection = SortDirection.Ascending; + public sortDirection: SortDirection = SortDirection.ASC; /** * Name of the field that's used to sort by diff --git a/src/app/shared/route.service.ts b/src/app/shared/route.service.ts index 10a7eaecb9..9c2b64ede1 100644 --- a/src/app/shared/route.service.ts +++ b/src/app/shared/route.service.ts @@ -13,19 +13,19 @@ export class RouteService { } getQueryParameterValues(paramName: string): Observable { - return this.route.queryParamMap.map((map) => [...map.getAll(paramName)]); + return this.route.queryParamMap.map((map) => [...map.getAll(paramName)]).distinctUntilChanged(); } getQueryParameterValue(paramName: string): Observable { - return this.route.queryParamMap.map((map) => map.get(paramName)); + return this.route.queryParamMap.map((map) => map.get(paramName)).distinctUntilChanged(); } hasQueryParam(paramName: string): Observable { - return this.route.queryParamMap.map((map) => map.has(paramName)); + return this.route.queryParamMap.map((map) => map.has(paramName)).distinctUntilChanged(); } hasQueryParamWithValue(paramName: string, paramValue: string): Observable { - return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1); + return this.route.queryParamMap.map((map) => map.getAll(paramName).indexOf(paramValue) > -1).distinctUntilChanged(); } getQueryParamsWithPrefix(prefix: string): Observable { @@ -38,6 +38,6 @@ export class RouteService { params[key] = [...map.getAll(key)]; }); return params; - }); + }).distinctUntilChanged(); } } From 545145a75938475da92f6056c25eab33452bb4b4 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 20 Apr 2018 16:01:59 +0200 Subject: [PATCH 03/13] fixed an issue where a search result without hitlighlights would break the grid view --- .../data/search-response-parsing.service.ts | 2 +- .../search-result-list-element.component.ts | 34 ++++++++----------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/app/core/data/search-response-parsing.service.ts b/src/app/core/data/search-response-parsing.service.ts index 707875911d..c7456aa2f9 100644 --- a/src/app/core/data/search-response-parsing.service.ts +++ b/src/app/core/data/search-response-parsing.service.ts @@ -26,7 +26,7 @@ export class SearchResponseParsingService implements ResponseParsingService { value: hhObject[key].join('...') })) } else { - return undefined; + return []; } }); diff --git a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts index 31e31c72a8..fd821997ad 100644 --- a/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts +++ b/src/app/shared/object-list/search-result-list-element/search-result-list-element.component.ts @@ -1,12 +1,12 @@ import { Component, Inject } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; import { SearchResult } from '../../../+search-page/search-result.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { Metadatum } from '../../../core/shared/metadatum.model'; -import { isEmpty, hasNoValue, isNotEmpty } from '../../empty.util'; +import { hasNoValue, isEmpty } from '../../empty.util'; import { ListableObject } from '../../object-collection/shared/listable-object.model'; import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component'; -import { Observable } from 'rxjs/Observable'; import { TruncatableService } from '../../truncatable/truncatable.service'; @Component({ @@ -24,15 +24,13 @@ export class SearchResultListElementComponent, K exten getValues(keys: string[]): string[] { const results: string[] = new Array(); - if (isNotEmpty(this.object.hitHighlights)) { - this.object.hitHighlights.forEach( - (md: Metadatum) => { - if (keys.indexOf(md.key) > -1) { - results.push(md.value); - } + this.object.hitHighlights.forEach( + (md: Metadatum) => { + if (keys.indexOf(md.key) > -1) { + results.push(md.value); } - ); - } + } + ); if (isEmpty(results)) { this.dso.filterMetadata(keys).forEach( (md: Metadatum) => { @@ -45,16 +43,14 @@ export class SearchResultListElementComponent, K exten getFirstValue(key: string): string { let result: string; - if (isNotEmpty(this.object.hitHighlights)) { - this.object.hitHighlights.some( - (md: Metadatum) => { - if (key === md.key) { - result = md.value; - return true; - } + this.object.hitHighlights.some( + (md: Metadatum) => { + if (key === md.key) { + result = md.value; + return true; } - ); - } + } + ); if (hasNoValue(result)) { result = this.dso.findMetadata(key); } From 994c0899255d2602c25a8633b2276b511328068a Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 10:57:12 +0200 Subject: [PATCH 04/13] fixed a few sorting, rpp and view persistence issues with grid view --- nodemon.json | 3 +- .../search-filter/search-filter.component.ts | 3 +- .../search-filter/search-filter.service.ts | 30 ++++---- .../search-filters.component.ts | 2 + .../+search-page/search-page.component.html | 4 +- .../search-page.component.spec.ts | 3 +- src/app/+search-page/search-page.component.ts | 1 - src/app/+search-page/search-page.module.ts | 2 + .../search-results.component.html | 4 +- .../search-service/search.service.ts | 3 +- .../search-settings.component.ts | 6 +- src/app/shared/host-window.service.ts | 72 +++++++++++++------ .../collection-grid-element.component.html | 8 +-- .../community-grid-element.component.html | 8 +-- .../item-grid-element.component.html | 4 +- .../object-grid/object-grid.component.html | 8 ++- .../object-grid/object-grid.component.scss | 37 +++++++--- .../object-grid/object-grid.component.ts | 64 +++++++++++++++-- ...-search-result-grid-element.component.html | 8 +-- ...-search-result-grid-element.component.html | 8 +-- .../search-form/search-form.component.spec.ts | 1 - .../search-form/search-form.component.ts | 22 +++--- webpack/webpack.common.js | 3 + 23 files changed, 208 insertions(+), 96 deletions(-) diff --git a/nodemon.json b/nodemon.json index 00313fe368..e76cdba1a4 100644 --- a/nodemon.json +++ b/nodemon.json @@ -4,5 +4,6 @@ "config", "src/index.html" ], - "ext": "js ts json html" + "ext": "js ts json html", + "delay": "500" } diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts index 90d3b50786..be26075d25 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.component.ts @@ -28,14 +28,13 @@ export class SearchFilterComponent implements OnInit { } ngOnInit() { - const sub = this.filterService.isFilterActive(this.filter.paramName).first().subscribe((isActive) => { + this.filterService.isFilterActive(this.filter.paramName).first().subscribe((isActive) => { if (this.filter.isOpenByDefault || isActive) { this.initialExpand(); } else { this.initialCollapse(); } }); - sub.unsubscribe(); } toggle() { diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 62f906c004..cbe6d79dfc 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@angular/core'; +import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import { SearchFiltersState, SearchFilterState } from './search-filter.reducer'; import { createSelector, MemoizedSelector, Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; @@ -79,20 +80,21 @@ export class SearchFilterService { this.getCurrentView(), this.getCurrentScope(), this.getCurrentQuery(), - this.getCurrentFilters(), - (pagination, sort, view, scope, query, filters) => { - return Object.assign(new PaginatedSearchOptions(), - defaults, - { - pagination: pagination, - sort: sort, - view: view, - scope: scope || defaults.scope, - query: query, - filters: filters - }) - } - ) + this.getCurrentFilters()).pipe( + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + map(([pagination, sort, view, scope, query, filters]) => { + return Object.assign(new PaginatedSearchOptions(), + defaults, + { + pagination: pagination, + sort: sort, + view: view, + scope: scope || defaults.scope, + query: query, + filters: filters + }) + }) + ) } getSearchOptions(defaults: any = {}): Observable { diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index 517b2e1e59..cb2dae0290 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -23,6 +23,8 @@ export class SearchFiltersComponent { constructor(private searchService: SearchService, private filterService: SearchFilterService) { this.filters = searchService.getConfig(); this.clearParams = filterService.getCurrentFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); + + this.filters.subscribe((v) => console.log('this.filters', v)); } getSearchLink() { diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index d53e4776b1..d911a4cb9b 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -7,7 +7,7 @@
@@ -36,4 +36,4 @@
- \ No newline at end of file + diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index cc53e18871..c51547a53d 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -38,7 +38,8 @@ describe('SearchPageComponent', () => { const sort: SortOptions = new SortOptions(); const mockResults = Observable.of(['test', 'data']); const searchServiceStub = jasmine.createSpyObj('SearchService', { - search: mockResults + search: mockResults, + getSearchLink: '/search' }); const queryParam = 'test query'; const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f'; diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index c9efc65676..6b2d9b95eb 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -31,7 +31,6 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; export class SearchPageComponent implements OnInit { resultsRD$: Observable>>>; - currentParams = {}; searchOptions$: Observable; sortConfig: SortOptions; scopeListRD$: Observable>>; diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index 7c2001c909..1468fe532e 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -1,5 +1,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; +import { CoreModule } from '../core/core.module'; import { SharedModule } from '../shared/shared.module'; import { SearchPageRoutingModule } from './search-page-routing.module'; import { SearchPageComponent } from './search-page.component'; @@ -31,6 +32,7 @@ const effects = [ CommonModule, SharedModule, EffectsModule.forFeature(effects), + CoreModule.forRoot() ], declarations: [ SearchPageComponent, diff --git a/src/app/+search-page/search-results/search-results.component.html b/src/app/+search-page/search-results/search-results.component.html index ec103e8957..ed6fc18d9c 100644 --- a/src/app/+search-page/search-results/search-results.component.html +++ b/src/app/+search-page/search-results/search-results.component.html @@ -1,5 +1,5 @@

{{ 'search.results.head' | translate }}

-
+
- \ No newline at end of file + diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 25b8c0b23e..07546c062c 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -167,7 +167,8 @@ export class SearchService implements OnDestroy { // get search results from response cache const facetConfigObs: Observable = responseCacheObs.pipe( map((entry: ResponseCacheEntry) => entry.response), - map((response: FacetConfigSuccessResponse) => response.results) + map((response: FacetConfigSuccessResponse) => + response.results.map((result: any) => Object.assign(new SearchFilterConfig(), result))) ); return this.rdb.toRemoteDataObservable(requestEntryObs, responseCacheObs, facetConfigObs); diff --git a/src/app/+search-page/search-settings/search-settings.component.ts b/src/app/+search-page/search-settings/search-settings.component.ts index cc22da7176..145b58e27b 100644 --- a/src/app/+search-page/search-settings/search-settings.component.ts +++ b/src/app/+search-page/search-settings/search-settings.component.ts @@ -22,8 +22,6 @@ export class SearchSettingsComponent implements OnInit { */ public pageSize; @Input() public pageSizeOptions; - public listPageSizeOptions: number[] = [5, 10, 20, 40, 60, 80, 100]; - public gridPageSizeOptions: number[] = [12, 24, 36, 48 , 50, 62, 74, 84]; private sub; private scope: string; @@ -51,9 +49,9 @@ export class SearchSettingsComponent implements OnInit { this.pageSize = +params.pageSize || this.searchOptions.pagination.pageSize; this.direction = params.sortDirection || this.searchOptions.sort.direction; if (params.view === ViewMode.Grid) { - this.pageSizeOptions = this.gridPageSizeOptions; + this.pageSizeOptions = this.pageSizeOptions; } else { - this.pageSizeOptions = this.listPageSizeOptions; + this.pageSizeOptions = this.pageSizeOptions; } }); } diff --git a/src/app/shared/host-window.service.ts b/src/app/shared/host-window.service.ts index 6fa5a6b32b..13ecbe7538 100644 --- a/src/app/shared/host-window.service.ts +++ b/src/app/shared/host-window.service.ts @@ -1,3 +1,4 @@ +import { distinctUntilChanged, map } from 'rxjs/operators'; import { HostWindowState } from './host-window.reducer'; import { Injectable } from '@angular/core'; import { createSelector, Store } from '@ngrx/store'; @@ -8,11 +9,18 @@ import { AppState } from '../app.reducer'; // TODO: ideally we should get these from sass somehow export enum GridBreakpoint { - XS = 0, - SM = 576, - MD = 768, - LG = 992, - XL = 1200 + SM_MIN = 576, + MD_MIN = 768, + LG_MIN = 992, + XL_MIN = 1200 +} + +export enum WidthCategory { + XS, + SM, + MD, + LG, + XL } const hostWindowStateSelector = (state: AppState) => state.hostWindow; @@ -31,33 +39,57 @@ export class HostWindowService { .filter((width) => hasValue(width)); } + get widthCategory(): Observable { + return this.getWidthObs().pipe( + map((width: number) => { + if (width < GridBreakpoint.SM_MIN) { + return WidthCategory.XS + } else if (width >= GridBreakpoint.SM_MIN && width < GridBreakpoint.MD_MIN) { + return WidthCategory.SM + } else if (width >= GridBreakpoint.MD_MIN && width < GridBreakpoint.LG_MIN) { + return WidthCategory.MD + } else if (width >= GridBreakpoint.LG_MIN && width < GridBreakpoint.XL_MIN) { + return WidthCategory.LG + } else { + return WidthCategory.XL + } + }), + distinctUntilChanged() + ); + } + isXs(): Observable { - return this.getWidthObs() - .map((width) => width < GridBreakpoint.SM) - .distinctUntilChanged(); + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === WidthCategory.XS), + distinctUntilChanged() + ); } isSm(): Observable { - return this.getWidthObs() - .map((width) => width >= GridBreakpoint.SM && width < GridBreakpoint.MD) - .distinctUntilChanged(); + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === WidthCategory.SM), + distinctUntilChanged() + ); } isMd(): Observable { - return this.getWidthObs() - .map((width) => width >= GridBreakpoint.MD && width < GridBreakpoint.LG) - .distinctUntilChanged(); + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === WidthCategory.MD), + distinctUntilChanged() + ); } isLg(): Observable { - return this.getWidthObs() - .map((width) => width >= GridBreakpoint.LG && width < GridBreakpoint.XL) - .distinctUntilChanged(); + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === WidthCategory.LG), + distinctUntilChanged() + ); } isXl(): Observable { - return this.getWidthObs() - .map((width) => width >= GridBreakpoint.XL) - .distinctUntilChanged(); + return this.widthCategory.pipe( + map((widthCat: WidthCategory) => widthCat === WidthCategory.XL), + distinctUntilChanged() + ); } } diff --git a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html index b1287212a3..9fecb51b9a 100644 --- a/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html +++ b/src/app/shared/object-grid/collection-grid-element/collection-grid-element.component.html @@ -1,13 +1,13 @@
- - - + + +

{{object.name}}

{{object.shortDescription}}

- View + View
diff --git a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html index b6f4c5c5d9..31a9e8ad3d 100644 --- a/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html +++ b/src/app/shared/object-grid/community-grid-element/community-grid-element.component.html @@ -1,14 +1,14 @@
- - - + + +

{{object.name}}

{{object.shortDescription}}

- View + View
diff --git a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html index 328bfc3bc9..cc2f2efdb1 100644 --- a/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html +++ b/src/app/shared/object-grid/item-grid-element/item-grid-element.component.html @@ -1,6 +1,6 @@
- + @@ -16,7 +16,7 @@

{{object.findMetadata("dc.description.abstract") | dsTruncate:[200] }}

- View + View
diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html index fcf3a42662..18aa2dd2e7 100644 --- a/src/app/shared/object-grid/object-grid.component.html +++ b/src/app/shared/object-grid/object-grid.component.html @@ -10,12 +10,14 @@ (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)"> -
-
+
+
+
+
+ diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss index 1b9418be48..33b158d971 100644 --- a/src/app/shared/object-grid/object-grid.component.scss +++ b/src/app/shared/object-grid/object-grid.component.scss @@ -11,14 +11,33 @@ ds-wrapper-grid-element ::ng-deep { } } +$gutter: ($grid-gutter-width / 2); +$min-width: 300px; +$max-cols: 3; + .card-columns { - @include media-breakpoint-only(lg) { - column-count: 3; + display: flex; + flex-wrap: wrap; + margin-left: -$gutter; + margin-top: -$gutter; + + .card-column { + flex: 1 0 $min-width; + margin-left: $gutter; + margin-top: $gutter; + + @for $i from 2 through $max-cols { + $screen-width: ($min-width*$i)+($gutter*$i); + $column-width: (100%/$i); + @media (min-width: $screen-width) { + max-width: calc(#{$column-width} - #{$gutter}); + } + } + + $column-width: (100%/$max-cols); + @media (min-width: $min-width*$max-cols) { + min-width: calc(#{$column-width} - #{$gutter}); + } } - @include media-breakpoint-only(sm) { - column-count: 2; - } - @include media-breakpoint-only(xs) { - column-count: 1; - } -} \ No newline at end of file +} + diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts index a8f8ebb183..bffa79b62b 100644 --- a/src/app/shared/object-grid/object-grid.component.ts +++ b/src/app/shared/object-grid/object-grid.component.ts @@ -2,16 +2,21 @@ import { ChangeDetectionStrategy, Component, EventEmitter, - Input, + Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Observable } from 'rxjs/Observable'; +import { map } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; import { RemoteData } from '../../core/data/remote-data'; import { fadeIn } from '../animations/fade'; +import { hasNoValue, hasValue } from '../empty.util'; +import { HostWindowService, WidthCategory } from '../host-window.service'; import { ListableObject } from '../object-collection/shared/listable-object.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; @@ -25,18 +30,18 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o animations: [fadeIn] }) -export class ObjectGridComponent { +export class ObjectGridComponent implements OnInit { @Input() config: PaginationComponentOptions; @Input() sortConfig: SortOptions; @Input() hideGear = false; @Input() hidePagerWhenSinglePage = true; - private _objects: RemoteData>; + private _objects$: BehaviorSubject>>; @Input() set objects(objects: RemoteData>) { - this._objects = objects; + this._objects$.next(objects); } get objects() { - return this._objects; + return this._objects$.getValue(); } /** @@ -77,6 +82,55 @@ export class ObjectGridComponent { */ @Output() sortFieldChange: EventEmitter = new EventEmitter(); data: any = {}; + columns$: Observable + + constructor(private hostWindow: HostWindowService) { + this._objects$ = new BehaviorSubject(undefined); + } + + ngOnInit(): void { + const nbColumns$ = this.hostWindow.widthCategory.pipe( + map((widthCat: WidthCategory) => { + switch (widthCat) { + case WidthCategory.XL: + case WidthCategory.LG: { + return 3; + } + case WidthCategory.MD: + case WidthCategory.SM: { + return 2; + } + default: { + return 1; + } + } + }) + ).startWith(3); + + this.columns$ = Observable.combineLatest( + nbColumns$, + this._objects$, + (nbColumns, objects) => { + if (hasValue(objects) && hasValue(objects.payload) && hasValue(objects.payload.page)) { + const page = objects.payload.page; + + const result = []; + + page.forEach((obj: ListableObject, i: number) => { + const colNb = i % nbColumns; + let col = result[colNb]; + if (hasNoValue(col)) { + col = []; + } + result[colNb] = [...col, obj]; + }); + return result; + } else { + return []; + } + }); + } + onPageChange(event) { this.pageChange.emit(event); } diff --git a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html index d6b1bfb5f4..91548d945d 100644 --- a/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component.html @@ -1,13 +1,13 @@
- - - + + +

{{dso.name}}

{{dso.shortDescription}}

- View + View
diff --git a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html index 8ff6874bff..95094a6fa1 100644 --- a/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component.html @@ -1,14 +1,14 @@
- - - + + +

{{dso.name}}

{{dso.shortDescription}}

- View + View
diff --git a/src/app/shared/search-form/search-form.component.spec.ts b/src/app/shared/search-form/search-form.component.spec.ts index d148429b01..30f5801cc2 100644 --- a/src/app/shared/search-form/search-form.component.spec.ts +++ b/src/app/shared/search-form/search-form.component.spec.ts @@ -2,7 +2,6 @@ import { ComponentFixture, TestBed, async, tick, fakeAsync } from '@angular/core import { By } from '@angular/platform-browser'; import { DebugElement } from '@angular/core'; import { SearchFormComponent } from './search-form.component'; -import { Observable } from 'rxjs/Observable'; import { FormsModule } from '@angular/forms'; import { ResourceType } from '../../core/shared/resource-type'; import { RouterTestingModule } from '@angular/router/testing'; diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index 49f825e28c..ed7572f94d 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -1,4 +1,5 @@ import { Component, Input } from '@angular/core'; +import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; import { isNotEmpty, hasValue, isEmpty } from '../empty.util'; @@ -17,8 +18,7 @@ import { isNotEmpty, hasValue, isEmpty } from '../empty.util'; export class SearchFormComponent { @Input() query: string; selectedId = ''; - // Optional existing search parameters - @Input() currentParams: {}; + @Input() currentUrl: string; @Input() scopes: DSpaceObject[]; @Input() @@ -34,16 +34,14 @@ export class SearchFormComponent { } updateSearch(data: any) { - this.router.navigate(['/search'], { - queryParams: Object.assign({}, this.currentParams, - { - query: data.query, - scope: data.scope || undefined, - page: data.page || 1 - } - ) - }) - ; + this.router.navigate([this.currentUrl], { + queryParams: { + query: data.query, + scope: data.scope || undefined, + page: data.page || 1 + }, + queryParamsHandling: 'merge' + }); } isNotEmpty(object: any) { diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 6bf4620ca7..904e52e0ff 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -13,6 +13,9 @@ module.exports = { output: { path: root('dist') }, + watchOptions: { + aggregateTimeout: 500, + }, module: { rules: [{ test: /\.ts$/, From 2d0a43f91f480e996d03487cab4fd568a5642c54 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 11:38:36 +0200 Subject: [PATCH 05/13] added tests for new HostWindowService code --- nodemon.json | 2 +- src/app/shared/host-window.service.spec.ts | 75 +++++++++++++++++++++- webpack/webpack.common.js | 2 +- 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/nodemon.json b/nodemon.json index e76cdba1a4..107ae1a754 100644 --- a/nodemon.json +++ b/nodemon.json @@ -5,5 +5,5 @@ "src/index.html" ], "ext": "js ts json html", - "delay": "500" + "delay": "50" } diff --git a/src/app/shared/host-window.service.spec.ts b/src/app/shared/host-window.service.spec.ts index 674d0e1332..41be3211e9 100644 --- a/src/app/shared/host-window.service.spec.ts +++ b/src/app/shared/host-window.service.spec.ts @@ -1,9 +1,10 @@ import { Store } from '@ngrx/store'; +import { cold, hot } from 'jasmine-marbles'; import { Observable } from 'rxjs/Observable'; import { AppState } from '../app.reducer'; import { HostWindowState } from './host-window.reducer'; -import { HostWindowService } from './host-window.service'; +import { GridBreakpoint, HostWindowService, WidthCategory } from './host-window.service'; describe('HostWindowService', () => { let service: HostWindowService; @@ -189,4 +190,76 @@ describe('HostWindowService', () => { }); }); + describe('widthCategory', () => { + beforeEach(() => { + service = new HostWindowService({} as Store); + }); + + it('should call getWithObs to get the current width', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { a: GridBreakpoint.SM_MIN - 1 })); + + const result = service.widthCategory; + + expect((service as any).getWidthObs).toHaveBeenCalled(); + }); + + it('should return XS if width < SM_MIN', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { a: GridBreakpoint.SM_MIN - 1 })); + + const result = service.widthCategory; + + const expected = cold('b-', { b: WidthCategory.XS }); + expect(result).toBeObservable(expected); + }); + + it('should return SM if SM_MIN <= width < MD_MIN', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { + a: GridBreakpoint.SM_MIN + Math.floor((GridBreakpoint.MD_MIN - GridBreakpoint.SM_MIN) / 2) + })); + + const result = service.widthCategory; + + const expected = cold('b-', { b: WidthCategory.SM }); + expect(result).toBeObservable(expected); + }); + + it('should return MD if MD_MIN <= width < LG_MIN', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { + a: GridBreakpoint.MD_MIN + Math.floor((GridBreakpoint.LG_MIN - GridBreakpoint.MD_MIN) / 2) + })); + + const result = service.widthCategory; + + const expected = cold('b-', { b: WidthCategory.MD }); + expect(result).toBeObservable(expected); + }); + + it('should return LG if LG_MIN <= width < XL_MIN', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { + a: GridBreakpoint.LG_MIN + Math.floor((GridBreakpoint.XL_MIN - GridBreakpoint.LG_MIN) / 2) + })); + + const result = service.widthCategory; + + const expected = cold('b-', { b: WidthCategory.LG }); + expect(result).toBeObservable(expected); + }); + + it('should return XL if width >= XL_MIN', () => { + spyOn(service as any, 'getWidthObs').and + .returnValue(hot('a-', { a: GridBreakpoint.XL_MIN + 1 })); + + const result = service.widthCategory; + + const expected = cold('b-', { b: WidthCategory.XL }); + expect(result).toBeObservable(expected); + }); + + }); + }); diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 904e52e0ff..3cbfe5c648 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -14,7 +14,7 @@ module.exports = { path: root('dist') }, watchOptions: { - aggregateTimeout: 500, + aggregateTimeout: 50, }, module: { rules: [{ From cb4c062ad9c843520cc75cbdd0c862ab51389c2d Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 11:58:32 +0200 Subject: [PATCH 06/13] fixed an issue with the AoT build --- src/app/+search-page/search-page.component.html | 2 +- src/app/+search-page/search-page.component.ts | 4 ++++ src/app/+search-page/search-service/search.service.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html index d911a4cb9b..1a1f379920 100644 --- a/src/app/+search-page/search-page.component.html +++ b/src/app/+search-page/search-page.component.html @@ -7,7 +7,7 @@
diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 6b2d9b95eb..95ff8f498f 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -77,4 +77,8 @@ export class SearchPageComponent implements OnInit { public isSidebarCollapsed(): Observable { return this.sidebarService.isCollapsed; } + + public getSearchLink(): string { + return this.service.getSearchLink(); + } } diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 07546c062c..2e1a6d5a6d 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -236,7 +236,7 @@ export class SearchService implements OnDestroy { this.router.navigate([this.getSearchLink()], navigationExtras); } - getSearchLink() { + getSearchLink(): string { const urlTree = this.router.parseUrl(this.router.url); const g: UrlSegmentGroup = urlTree.root.children[PRIMARY_OUTLET]; return '/' + g.toString(); From 5e2dc51828fff03922f7511fb7ec59bdb8a8926a Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 13:27:15 +0200 Subject: [PATCH 07/13] replaced css with bootstrap classes --- .../object-grid/object-grid.component.html | 4 +-- .../object-grid/object-grid.component.scss | 33 +++++-------------- .../object-grid/object-grid.component.ts | 5 +-- ...-search-result-grid-element.component.html | 4 +-- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/app/shared/object-grid/object-grid.component.html b/src/app/shared/object-grid/object-grid.component.html index 18aa2dd2e7..9d1f8f5ea2 100644 --- a/src/app/shared/object-grid/object-grid.component.html +++ b/src/app/shared/object-grid/object-grid.component.html @@ -10,8 +10,8 @@ (sortDirectionChange)="onSortDirectionChange($event)" (sortFieldChange)="onSortFieldChange($event)" (paginationChange)="onPaginationChange($event)"> -
-
+
+
diff --git a/src/app/shared/object-grid/object-grid.component.scss b/src/app/shared/object-grid/object-grid.component.scss index 33b158d971..ff78634863 100644 --- a/src/app/shared/object-grid/object-grid.component.scss +++ b/src/app/shared/object-grid/object-grid.component.scss @@ -1,43 +1,26 @@ @import '../../../styles/variables'; @import '../../../styles/mixins'; +$ds-wrapper-grid-spacing: $spacer/2; + ds-wrapper-grid-element ::ng-deep { div.thumbnail > img { height: $card-thumbnail-height; width: 100%; } div.card { - margin-bottom: $spacer; + margin-top: $ds-wrapper-grid-spacing; + margin-bottom: $ds-wrapper-grid-spacing; } } -$gutter: ($grid-gutter-width / 2); -$min-width: 300px; -$max-cols: 3; - .card-columns { - display: flex; - flex-wrap: wrap; - margin-left: -$gutter; - margin-top: -$gutter; + margin-left: -$ds-wrapper-grid-spacing; + margin-right: -$ds-wrapper-grid-spacing; .card-column { - flex: 1 0 $min-width; - margin-left: $gutter; - margin-top: $gutter; - - @for $i from 2 through $max-cols { - $screen-width: ($min-width*$i)+($gutter*$i); - $column-width: (100%/$i); - @media (min-width: $screen-width) { - max-width: calc(#{$column-width} - #{$gutter}); - } - } - - $column-width: (100%/$max-cols); - @media (min-width: $min-width*$max-cols) { - min-width: calc(#{$column-width} - #{$gutter}); - } + padding-left: $ds-wrapper-grid-spacing; + padding-right: $ds-wrapper-grid-spacing; } } diff --git a/src/app/shared/object-grid/object-grid.component.ts b/src/app/shared/object-grid/object-grid.component.ts index bffa79b62b..4c4add9b06 100644 --- a/src/app/shared/object-grid/object-grid.component.ts +++ b/src/app/shared/object-grid/object-grid.component.ts @@ -8,7 +8,7 @@ import { } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; -import { map } from 'rxjs/operators'; +import { distinctUntilChanged, map } from 'rxjs/operators'; import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { PaginatedList } from '../../core/data/paginated-list'; @@ -104,7 +104,8 @@ export class ObjectGridComponent implements OnInit { return 1; } } - }) + }), + distinctUntilChanged() ).startWith(3); this.columns$ = Observable.combineLatest( diff --git a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html index b185caa18f..1cf14587ad 100644 --- a/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html +++ b/src/app/shared/object-grid/search-result-grid-element/item-search-result/item-search-result-grid-element.component.html @@ -1,5 +1,5 @@ - - \ No newline at end of file + From 5ad190c9060cc504a69e46ca116a98d2b4c20dc2 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 13:37:14 +0200 Subject: [PATCH 08/13] fixed an issue where the search form wouldn't work on any page but the search page --- src/app/shared/search-form/search-form.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/shared/search-form/search-form.component.ts b/src/app/shared/search-form/search-form.component.ts index ed7572f94d..5fd984a731 100644 --- a/src/app/shared/search-form/search-form.component.ts +++ b/src/app/shared/search-form/search-form.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; import { SearchService } from '../../+search-page/search-service/search.service'; import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { Router } from '@angular/router'; -import { isNotEmpty, hasValue, isEmpty } from '../empty.util'; +import { isNotEmpty, hasValue, isEmpty, hasNoValue } from '../empty.util'; /** * This component renders a simple item page. @@ -34,7 +34,8 @@ export class SearchFormComponent { } updateSearch(data: any) { - this.router.navigate([this.currentUrl], { + const newUrl = hasValue(this.currentUrl) ? this.currentUrl : 'search'; + this.router.navigate([newUrl], { queryParams: { query: data.query, scope: data.scope || undefined, From d6fc8aca4fd29841387e8b776ee2ed6e5da2d0bd Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 14:36:19 +0200 Subject: [PATCH 09/13] added support for Object.entries, removed a console.log and fixed an issue where the page wouldn't reset after selecting a new filter --- .../search-facet-filter.component.spec.ts | 4 ++-- .../search-facet-filter.component.ts | 10 ++++++++-- .../search-filters/search-filters.component.ts | 2 -- src/app/+search-page/search-options.model.ts | 1 + tsconfig.json | 3 ++- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts index f91332929e..03b760318f 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.spec.ts @@ -124,14 +124,14 @@ describe('SearchFacetFilterComponent', () => { describe('when the getAddParams method is called wih a value', () => { it('should return the selectedValue list with the new parameter value', () => { const result = comp.getAddParams(value3); - expect(result).toEqual({ [mockFilterConfig.paramName]: [value1, value2, value3] }); + expect(result[mockFilterConfig.paramName]).toEqual([value1, value2, value3]); }); }); 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); - expect(result).toEqual({ [mockFilterConfig.paramName]: [value2] }); + expect(result[mockFilterConfig.paramName]).toEqual([value2]); }); }); diff --git a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts index bf78bbe5ec..5f8111c87b 100644 --- a/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts +++ b/src/app/+search-page/search-filters/search-filter/search-facet-filter/search-facet-filter.component.ts @@ -100,11 +100,17 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy { return hasValue(o); } getRemoveParams(value: string) { - return { [this.filterConfig.paramName]: this.selectedValues.filter((v) => v !== value) }; + return { + [this.filterConfig.paramName]: this.selectedValues.filter((v) => v !== value), + page: 1 + }; } getAddParams(value: string) { - return { [this.filterConfig.paramName]: [...this.selectedValues, value] }; + return { + [this.filterConfig.paramName]: [...this.selectedValues, value], + page: 1 + }; } ngOnDestroy(): void { diff --git a/src/app/+search-page/search-filters/search-filters.component.ts b/src/app/+search-page/search-filters/search-filters.component.ts index cb2dae0290..517b2e1e59 100644 --- a/src/app/+search-page/search-filters/search-filters.component.ts +++ b/src/app/+search-page/search-filters/search-filters.component.ts @@ -23,8 +23,6 @@ export class SearchFiltersComponent { constructor(private searchService: SearchService, private filterService: SearchFilterService) { this.filters = searchService.getConfig(); this.clearParams = filterService.getCurrentFilters().map((filters) => {Object.keys(filters).forEach((f) => filters[f] = null); return filters;}); - - this.filters.subscribe((v) => console.log('this.filters', v)); } getSearchLink() { diff --git a/src/app/+search-page/search-options.model.ts b/src/app/+search-page/search-options.model.ts index 4164321680..df8d8e713a 100644 --- a/src/app/+search-page/search-options.model.ts +++ b/src/app/+search-page/search-options.model.ts @@ -1,5 +1,6 @@ import { isNotEmpty } from '../shared/empty.util'; import { URLCombiner } from '../core/url-combiner/url-combiner'; +import 'core-js/fn/object/entries'; export enum ViewMode { List = 'list', diff --git a/tsconfig.json b/tsconfig.json index 8037c659ed..8ab72a4327 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,8 @@ "es6", "es2015", "es2016", - "es2017" + "es2017", + "es2017.object" ] }, "exclude": [ From ebbe2e6a93bc6ee2cb9f8e2fbb327b22e3614a20 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 26 Apr 2018 15:51:36 +0200 Subject: [PATCH 10/13] added tests for the new object-grid-component onInit code --- .../object-grid/object-grid.component.spec.ts | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/src/app/shared/object-grid/object-grid.component.spec.ts b/src/app/shared/object-grid/object-grid.component.spec.ts index e69de29bb2..2ced28718a 100644 --- a/src/app/shared/object-grid/object-grid.component.spec.ts +++ b/src/app/shared/object-grid/object-grid.component.spec.ts @@ -0,0 +1,224 @@ +import { cold, hot } from 'jasmine-marbles'; +import { map } from 'rxjs/operators'; +import { WidthCategory } from '../host-window.service'; +import { ObjectGridComponent } from './object-grid.component'; + +describe('ObjectGridComponent', () => { + const testObjects = [ + { one: 1 }, + { two: 2 }, + { three: 3 }, + { four: 4 }, + { five: 5 }, + { six: 6 }, + { seven: 7 }, + { eight: 8 }, + { nine: 9 }, + { ten: 10 } + ]; + const mockRD = { + payload: { + page: testObjects + } + } as any; + + describe('the number of columns', () => { + + it('should be 3 for xl screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.XL }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: 3 }); + + const result = comp.columns$.pipe( + map((columns) => columns.length) + ); + + expect(result).toBeObservable(expected); + }); + + it('should be 3 for lg screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.LG }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: 3 }); + + const result = comp.columns$.pipe( + map((columns) => columns.length) + ); + + expect(result).toBeObservable(expected); + }); + + it('should be 2 for md screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.MD }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: 2 }); + + const result = comp.columns$.pipe( + map((columns) => columns.length) + ); + + expect(result).toBeObservable(expected); + }); + + it('should be 2 for sm screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.SM }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: 2 }); + + const result = comp.columns$.pipe( + map((columns) => columns.length) + ); + + expect(result).toBeObservable(expected); + }); + + it('should be 1 for xs screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.XS }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: 1 }); + + const result = comp.columns$.pipe( + map((columns) => columns.length) + ); + + expect(result).toBeObservable(expected); + }); + + }); + + describe('The ordering of the content', () => { + it('should be left to right for XL screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.XL }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: [ + [testObjects[0], testObjects[3], testObjects[6], testObjects[9]], + [testObjects[1], testObjects[4], testObjects[7]], + [testObjects[2], testObjects[5], testObjects[8]] + ] }); + + const result = comp.columns$; + + expect(result).toBeObservable(expected); + }); + + it('should be left to right for LG screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.LG }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: [ + [testObjects[0], testObjects[3], testObjects[6], testObjects[9]], + [testObjects[1], testObjects[4], testObjects[7]], + [testObjects[2], testObjects[5], testObjects[8]] + ] }); + + const result = comp.columns$; + + expect(result).toBeObservable(expected); + }); + + it('should be left to right for MD screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.MD }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: [ + [testObjects[0], testObjects[2], testObjects[4], testObjects[6], testObjects[8]], + [testObjects[1], testObjects[3], testObjects[5], testObjects[7], testObjects[9]], + ] }); + + const result = comp.columns$; + + expect(result).toBeObservable(expected); + }); + + it('should be left to right for SM screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.SM }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: [ + [testObjects[0], testObjects[2], testObjects[4], testObjects[6], testObjects[8]], + [testObjects[1], testObjects[3], testObjects[5], testObjects[7], testObjects[9]], + ] }); + + const result = comp.columns$; + + expect(result).toBeObservable(expected); + }); + + it('should be top to bottom for XS screens', () => { + const hostWindowService = { + widthCategory: hot('a', { a: WidthCategory.XS }), + } as any; + const comp = new ObjectGridComponent(hostWindowService); + + (comp as any)._objects$ = hot('b', { b: mockRD }); + + comp.ngOnInit(); + + const expected = cold('c', { c: [ testObjects ] }); + + const result = comp.columns$; + + expect(result).toBeObservable(expected); + }); + }); +}); From 38efd5b542423e8ccb3e768659ac520c3647da08 Mon Sep 17 00:00:00 2001 From: Lotte Hofstede Date: Thu, 3 May 2018 09:32:48 +0200 Subject: [PATCH 11/13] changed default sort options --- .../+collection-page/collection-page.component.ts | 4 ++-- .../top-level-community-list.component.ts | 4 ++-- .../search-filter/search-filter.service.ts | 12 ++++++++---- src/app/+search-page/search-page.component.ts | 3 ++- .../+search-page/search-service/search.service.ts | 4 ++-- src/app/core/cache/models/sort-options.model.ts | 2 +- .../shared/pagination/pagination.component.spec.ts | 4 ++-- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index de7e9a72d4..4a935b73b9 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; -import { SortOptions } from '../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { CollectionDataService } from '../core/data/collection-data.service'; import { ItemDataService } from '../core/data/item-data.service'; import { PaginatedList } from '../core/data/paginated-list'; @@ -48,7 +48,7 @@ export class CollectionPageComponent implements OnInit, OnDestroy { this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.pageSize = 5; this.paginationConfig.currentPage = 1; - this.sortConfig = new SortOptions(); + this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); } ngOnInit(): void { diff --git a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts index 1b71220382..8fca66ea79 100644 --- a/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts +++ b/src/app/+home-page/top-level-community-list/top-level-community-list.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { CommunityDataService } from '../../core/data/community-data.service'; import { PaginatedList } from '../../core/data/paginated-list'; @@ -27,7 +27,7 @@ export class TopLevelCommunityListComponent { this.config.id = 'top-level-pagination'; this.config.pageSize = 5; this.config.currentPage = 1; - this.sortConfig = new SortOptions(); + this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.updatePage({ page: this.config.currentPage, diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index cbe6d79dfc..890b32b2f0 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -60,8 +60,12 @@ export class SearchFilterService { getCurrentSort(): Observable { const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); const sortField$ = this.routeService.getQueryParameterValue('sortField'); - return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => - new SortOptions(isNotEmpty(sortField) ? sortField : undefined, SortDirection[sortDirection]) + return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => { + if (isNotEmpty(sortField)) { + const direction = SortDirection[sortDirection]; + return new SortOptions(sortField, direction ? direction : SortDirection.ASC) + } + } ); } @@ -87,7 +91,7 @@ export class SearchFilterService { defaults, { pagination: pagination, - sort: sort, + sort: sort || defaults.sort, view: view, scope: scope || defaults.scope, query: query, @@ -108,7 +112,7 @@ export class SearchFilterService { defaults, { view: view, - scope: scope, + scope: scope || defaults.scope, query: query, filters: filters }) diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts index 95ff8f498f..4f50723ced 100644 --- a/src/app/+search-page/search-page.component.ts +++ b/src/app/+search-page/search-page.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { flatMap, } from 'rxjs/operators'; -import { SortOptions } from '../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { CommunityDataService } from '../core/data/community-data.service'; import { PaginatedList } from '../core/data/paginated-list'; import { RemoteData } from '../core/data/remote-data'; @@ -42,6 +42,7 @@ export class SearchPageComponent implements OnInit { id: 'search-results-pagination', pageSize: 10 }, + sort: new SortOptions('score', SortDirection.DESC), query: '', scope: '' }; diff --git a/src/app/+search-page/search-service/search.service.ts b/src/app/+search-page/search-service/search.service.ts index 2e1a6d5a6d..0351a9a54c 100644 --- a/src/app/+search-page/search-service/search.service.ts +++ b/src/app/+search-page/search-service/search.service.ts @@ -7,7 +7,7 @@ import { Observable } from 'rxjs/Observable'; import { flatMap, map, tap } from 'rxjs/operators'; import { ViewMode } from '../../+search-page/search-options.model'; import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { FacetConfigSuccessResponse, FacetValueSuccessResponse, @@ -61,7 +61,7 @@ export class SearchService implements OnDestroy { pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; - const sort: SortOptions = new SortOptions(); + const sort: SortOptions = new SortOptions('score', SortDirection.DESC); this.searchOptions = Object.assign(new SearchOptions(), { pagination: pagination, sort: sort }); } diff --git a/src/app/core/cache/models/sort-options.model.ts b/src/app/core/cache/models/sort-options.model.ts index b2380cbaf3..247504a63a 100644 --- a/src/app/core/cache/models/sort-options.model.ts +++ b/src/app/core/cache/models/sort-options.model.ts @@ -4,7 +4,7 @@ export enum SortDirection { } export class SortOptions { - constructor(public field: string = 'dc.title', public direction: SortDirection = SortDirection.ASC) { + constructor(public field: string, public direction: SortDirection) { } } diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index ad05f0cdfe..48767cf582 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -41,7 +41,7 @@ import { MockRouter } from '../mocks/mock-router'; import { HostWindowService } from '../host-window.service'; import { EnumKeysPipe } from '../utils/enum-keys-pipe'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config'; @@ -349,7 +349,7 @@ class TestComponent { collection: string[] = []; collectionSize: number; paginationOptions = new PaginationComponentOptions(); - sortOptions = new SortOptions(); + sortOptions = new SortOptions('dc.title', SortDirection.ASC); constructor() { this.collection = Array.from(new Array(100), (x, i) => `item ${i + 1}`); From a4b2229509be2eaf47aafe6c1eeebbdcc3499a49 Mon Sep 17 00:00:00 2001 From: Lotte Hofstede Date: Thu, 3 May 2018 09:48:34 +0200 Subject: [PATCH 12/13] fixed prod build issue --- src/app/+search-page/search-page.component.spec.ts | 4 +--- .../search-settings/search-settings.component.spec.ts | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index c51547a53d..51c3a452e4 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -8,10 +8,8 @@ import { cold, hot } from 'jasmine-marbles'; import { Observable } from 'rxjs/Observable'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; import { CommunityDataService } from '../core/data/community-data.service'; -import { Community } from '../core/shared/community.model'; import { HostWindowService } from '../shared/host-window.service'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; import { SearchPageComponent } from './search-page.component'; import { SearchService } from './search-service/search.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @@ -35,7 +33,7 @@ describe('SearchPageComponent', () => { pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; - const sort: SortOptions = new SortOptions(); + const sort: SortOptions = new SortOptions('score', SortDirection.DESC); const mockResults = Observable.of(['test', 'data']); const searchServiceStub = jasmine.createSpyObj('SearchService', { search: mockResults, diff --git a/src/app/+search-page/search-settings/search-settings.component.spec.ts b/src/app/+search-page/search-settings/search-settings.component.spec.ts index 504bfbc2bf..2330b62669 100644 --- a/src/app/+search-page/search-settings/search-settings.component.spec.ts +++ b/src/app/+search-page/search-settings/search-settings.component.spec.ts @@ -3,7 +3,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SearchSettingsComponent } from './search-settings.component'; import { Observable } from 'rxjs/Observable'; import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model'; -import { SortOptions } from '../../core/cache/models/sort-options.model'; +import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model'; import { TranslateModule } from '@ngx-translate/core'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute } from '@angular/router'; @@ -22,7 +22,7 @@ describe('SearchSettingsComponent', () => { pagination.id = 'search-results-pagination'; pagination.currentPage = 1; pagination.pageSize = 10; - const sort: SortOptions = new SortOptions(); + const sort: SortOptions = new SortOptions('score', SortDirection.DESC); const mockResults = [ 'test', 'data' ]; const searchServiceStub = { searchOptions: { pagination: pagination, sort: sort }, From b065df356aa45516fdfb3bfc88764e4c47252c19 Mon Sep 17 00:00:00 2001 From: Lotte Hofstede Date: Thu, 3 May 2018 10:21:31 +0200 Subject: [PATCH 13/13] fixed sorting issue --- .../search-filter/search-filter.service.ts | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts index 890b32b2f0..44d9c7e709 100644 --- a/src/app/+search-page/search-filters/search-filter/search-filter.service.ts +++ b/src/app/+search-page/search-filters/search-filter/search-filter.service.ts @@ -57,14 +57,13 @@ export class SearchFilterService { }); } - getCurrentSort(): Observable { + getCurrentSort(defaultSort: SortOptions): Observable { const sortDirection$ = this.routeService.getQueryParameterValue('sortDirection'); const sortField$ = this.routeService.getQueryParameterValue('sortField'); return Observable.combineLatest(sortDirection$, sortField$, (sortDirection, sortField) => { - if (isNotEmpty(sortField)) { - const direction = SortDirection[sortDirection]; - return new SortOptions(sortField, direction ? direction : SortDirection.ASC) - } + const field = sortField || defaultSort.field; + const direction = SortDirection[sortDirection] || defaultSort.direction; + return new SortOptions(field, direction) } ); } @@ -80,28 +79,28 @@ export class SearchFilterService { getPaginatedSearchOptions(defaults: any = {}): Observable { return Observable.combineLatest( this.getCurrentPagination(defaults.pagination), - this.getCurrentSort(), + this.getCurrentSort(defaults.sort), this.getCurrentView(), this.getCurrentScope(), this.getCurrentQuery(), this.getCurrentFilters()).pipe( - distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), - map(([pagination, sort, view, scope, query, filters]) => { - return Object.assign(new PaginatedSearchOptions(), - defaults, - { - pagination: pagination, - sort: sort || defaults.sort, - view: view, - scope: scope || defaults.scope, - query: query, - filters: filters - }) - }) - ) + distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), + map(([pagination, sort, view, scope, query, filters]) => { + return Object.assign(new PaginatedSearchOptions(), + defaults, + { + pagination: pagination, + sort: sort, + view: view, + scope: scope || defaults.scope, + query: query, + filters: filters + }) + }) + ) } - getSearchOptions(defaults: any = {}): Observable { + getSearchOptions(defaults: any = {}): Observable { return Observable.combineLatest( this.getCurrentView(), this.getCurrentScope(),