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$/,