Merge remote-tracking branch 'remotes/origin/master' into submission-miscellaneous-fixes

This commit is contained in:
Giuseppe Digilio
2019-09-13 17:26:16 +02:00
106 changed files with 524 additions and 264 deletions

View File

@@ -1,5 +1,37 @@
sudo: required
dist: trusty
env:
# Install the latest docker-compose version for ci testing.
# The default installation in travis is not compatible with the latest docker-compose file version.
COMPOSE_VERSION: 1.24.1
# The ci step will test the dspace-angular code against DSpace REST.
# Direct that step to utilize a DSpace REST service that has been started in docker.
DSPACE_REST_HOST: localhost
DSPACE_REST_PORT: 8080
DSPACE_REST_NAMESPACE: '/server/api'
DSPACE_REST_SSL: false
before_install:
# Docker Compose Install
- curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
- chmod +x docker-compose
- sudo mv docker-compose /usr/local/bin
- git clone https://github.com/DSpace-Labs/DSpace-Docker-Images.git
install:
- docker-compose version
- docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml up -d
- travis_retry yarn install
before_script:
# The following line could be enabled to verify that the rest server is responding.
# Currently, "yarn run build" takes enough time to run to allow the service to be available
#- curl http://localhost:8080/
after_script:
- docker-compose -f DSpace-Docker-Images/docker-compose-files/dspace-compose/d7.travis.yml down
addons:
apt:
sources:
@@ -18,9 +50,6 @@ cache:
bundler_args: --retry 5
install:
- travis_retry yarn install
script:
# Use Chromium instead of Chrome.
- export CHROME_BIN=chromium-browser

View File

@@ -1,3 +1,4 @@
// This configuration is currently only being used for unit tests, end-to-end tests use environment.dev.ts
module.exports = {
};

View File

@@ -22,10 +22,10 @@
"clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld",
"clean": "yarn run clean:prod && yarn run clean:node",
"prebuild": "yarn run clean:bld && yarn run clean:dist",
"prebuild:aot": "yarn run prebuild",
"prebuild:ci": "yarn run prebuild",
"prebuild:prod": "yarn run prebuild",
"build": "node ./scripts/webpack.js --progress --mode development",
"build:aot": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development",
"build:ci": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development",
"build:prod": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode production && node ./scripts/webpack.js --env.aot --env.client --mode production",
"postbuild:prod": "yarn run rollup",
"rollup": "rollup -c rollup.config.js",
@@ -51,10 +51,13 @@
"debug:server": "node-nightly --inspect --debug-brk dist/server.js",
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --mode development",
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production",
"ci": "yarn run lint && yarn run build:aot && yarn run test:headless",
"ci": "yarn run lint && yarn run build:ci && yarn run test:headless && npm-run-all -p -r server e2e",
"protractor": "node node_modules/protractor/bin/protractor",
"pree2e": "yarn run webdriver:update",
"e2e": "yarn run protractor",
"pretest": "yarn run clean:bld",
"pretest:headless": "yarn run pretest",
"pretest:watch": "yarn run pretest",
"test": "karma start --single-run",
"test:headless": "karma start --single-run --browsers ChromeHeadless",
"test:watch": "karma start --no-single-run --auto-watch",

View File

@@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { SharedModule } from '../../shared/shared.module';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { of as observableOf } from 'rxjs';

View File

@@ -1,6 +1,6 @@
import { Component } from '@angular/core';
import { CommunityDataService } from '../../core/data/community-data.service';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { Router } from '@angular/router';
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
import { Collection } from '../../core/shared/collection.model';

View File

@@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { SharedModule } from '../../shared/shared.module';
import { CollectionDataService } from '../../core/data/collection-data.service';
import { of as observableOf } from 'rxjs';

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { Community } from '../../core/shared/community.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { Router } from '@angular/router';
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';

View File

@@ -4,7 +4,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { RouterTestingModule } from '@angular/router/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { SharedModule } from '../../shared/shared.module';
import { of as observableOf } from 'rxjs';
import { NotificationsService } from '../../shared/notifications/notifications.service';

View File

@@ -8,7 +8,7 @@ import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-
import { RoleService } from '../core/roles/role.service';
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model';
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
import { RouteService } from '../shared/services/route.service';
import { RouteService } from '../core/services/route.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';

View File

@@ -17,7 +17,7 @@ import { HostWindowService } from '../shared/host-window.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { RemoteData } from '../core/data/remote-data';
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component';
import { RouteService } from '../shared/services/route.service';
import { RouteService } from '../core/services/route.service';
import { routeServiceStub } from '../shared/testing/route-service-stub';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { SearchService } from '../+search-page/search-service/search.service';

View File

@@ -4,12 +4,12 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
import { SearchPageComponent } from './search-page.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
import { RouteService } from '../shared/services/route.service';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { Observable } from 'rxjs';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { map } from 'rxjs/operators';
import { RouteService } from '../core/services/route.service';
/**
* This component renders a search page using a configuration as input.

View File

@@ -4,12 +4,12 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
import { SearchPageComponent } from './search-page.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
import { RouteService } from '../shared/services/route.service';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { Observable } from 'rxjs';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { map } from 'rxjs/operators';
import { RouteService } from '../core/services/route.service';
/**
* This component renders a simple item page.

View File

@@ -17,7 +17,7 @@
</div>
<ds-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate"
[action]="getCurrentUrl()"
[action]="currentUrl"
[name]="filterConfig.paramName"
[(ngModel)]="filter"
(submitSuggestion)="onSubmit($event)"

View File

@@ -1,5 +1,5 @@
<a *ngIf="isVisible | async" class="d-flex flex-row"
[routerLink]="[getSearchLink()]"
[routerLink]="[searchLink]"
[queryParams]="addQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="false" class="my-1 align-self-stretch"/>
<span class="filter-value px-1">{{filterValue.value}}</span>

View File

@@ -50,6 +50,10 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
*/
addQueryParams;
/**
* Link to the search page
*/
searchLink: string;
/**
* Subscription to unsubscribe from on destroy
*/
@@ -66,6 +70,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
* Initializes all observable instance variables and starts listening to them
*/
ngOnInit(): void {
this.searchLink = this.getSearchLink();
this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked));
this.sub = observableCombineLatest(this.selectedValues$, this.searchConfigService.searchOptions)
.subscribe(([selectedValues, searchOptions]) => {
@@ -83,7 +88,7 @@ export class SearchFacetOptionComponent implements OnInit, OnDestroy {
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}

View File

@@ -1,5 +1,5 @@
<a *ngIf="isVisible | async" class="d-flex flex-row"
[routerLink]="[getSearchLink()]"
[routerLink]="[searchLink]"
[queryParams]="changeQueryParams" queryParamsHandling="merge">
<span class="filter-value px-1">{{filterValue.label}}</span>
<span class="float-right filter-value-count ml-auto">

View File

@@ -56,6 +56,11 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy {
*/
sub: Subscription;
/**
* Link to the search page
*/
searchLink: string;
constructor(protected searchService: SearchService,
protected filterService: SearchFilterService,
protected searchConfigService: SearchConfigurationService,
@@ -67,6 +72,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy {
* Initializes all observable instance variables and starts listening to them
*/
ngOnInit(): void {
this.searchLink = this.getSearchLink();
this.isVisible = this.isChecked().pipe(map((checked: boolean) => !checked));
this.sub = this.searchConfigService.searchOptions.subscribe(() => {
this.updateChangeParams()
@@ -83,7 +89,7 @@ export class SearchFacetRangeOptionComponent implements OnInit, OnDestroy {
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}

View File

@@ -1,5 +1,5 @@
<a class="d-flex flex-row"
[routerLink]="[getSearchLink()]"
[routerLink]="[searchLink]"
[queryParams]="removeQueryParams" queryParamsHandling="merge">
<input type="checkbox" [checked]="true" class="my-1 align-self-stretch"/>
<span class="filter-value pl-1 text-capitalize">{{selectedValue.label}}</span>

View File

@@ -49,6 +49,11 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy {
*/
sub: Subscription;
/**
* Link to the search page
*/
searchLink: string;
constructor(protected searchService: SearchService,
protected filterService: SearchFilterService,
protected searchConfigService: SearchConfigurationService,
@@ -64,12 +69,13 @@ export class SearchFacetSelectedOptionComponent implements OnInit, OnDestroy {
.subscribe(([selectedValues, searchOptions]) => {
this.updateRemoveParams(selectedValues)
});
this.searchLink = this.getSearchLink();
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}

View File

@@ -80,6 +80,11 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
*/
searchOptions$: Observable<SearchOptions>;
/**
* The current URL
*/
currentUrl: string;
constructor(protected searchService: SearchService,
protected filterService: SearchFilterService,
protected rdbs: RemoteDataBuildService,
@@ -93,6 +98,7 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
* Initializes all observable instance variables and starts listening to them
*/
ngOnInit(): void {
this.currentUrl = this.router.url;
this.filterValues$ = new BehaviorSubject(new RemoteData(true, false, undefined, undefined, undefined));
this.currentPage = this.getCurrentPage().pipe(distinctUntilChanged());
@@ -215,13 +221,6 @@ export class SearchFacetFilterComponent implements OnInit, OnDestroy {
return this.filterService.getPage(this.filterConfig.name);
}
/**
* @returns {string} the current URL
*/
getCurrentUrl() {
return this.router.url;
}
/**
* Submits a new active custom value to the filter from the input field
* @param data The string from the input field

View File

@@ -1,5 +1,5 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { mergeMap, map, distinctUntilChanged } from 'rxjs/operators';
import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators';
import { Injectable, InjectionToken } from '@angular/core';
import { SearchFiltersState, SearchFilterState } from './search-filter.reducer';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
@@ -14,16 +14,12 @@ import {
} from './search-filter.actions';
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
import { SearchFilterConfig } from '../../search-service/search-filter-config.model';
import { RouteService } from '../../../shared/services/route.service';
import { RouteService } from '../../../core/services/route.service';
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SearchOptions } from '../../search-options.model';
import { PaginatedSearchOptions } from '../../paginated-search-options.model';
import { SearchFixedFilterService } from './search-fixed-filter.service';
import { Params } from '@angular/router';
import * as postcss from 'postcss';
import prefix = postcss.vendor.prefix;
// const spy = create();
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
export const FILTER_CONFIG: InjectionToken<SearchFilterConfig> = new InjectionToken<SearchFilterConfig>('filterConfig');

View File

@@ -1,20 +1,19 @@
import { SearchFixedFilterService } from './search-fixed-filter.service';
import { RouteService } from '../../../shared/services/route.service';
import { RequestService } from '../../../core/data/request.service';
import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
import { of as observableOf } from 'rxjs';
import { RequestEntry } from '../../../core/data/request.reducer';
import { FilteredDiscoveryQueryResponse, RestResponse } from '../../../core/cache/response.models';
import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models';
describe('SearchFixedFilterService', () => {
let service: SearchFixedFilterService;
const filterQuery = 'filter:query';
const routeServiceStub = {} as RouteService;
const requestServiceStub = Object.assign({
/* tslint:disable:no-empty */
configure: () => {},
configure: () => {
},
/* tslint:enable:no-empty */
generateRequestId: () => 'fake-id',
getByHref: () => observableOf(Object.assign(new RequestEntry(), {
@@ -26,7 +25,7 @@ describe('SearchFixedFilterService', () => {
});
beforeEach(() => {
service = new SearchFixedFilterService(routeServiceStub, requestServiceStub, halServiceStub);
service = new SearchFixedFilterService(requestServiceStub, halServiceStub);
});
describe('when getQueryByFilterName is called with a filterName', () => {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { flatMap, map, switchMap, tap } from 'rxjs/operators';
import { map, switchMap } from 'rxjs/operators';
import { Observable, of as observableOf } from 'rxjs';
import { HALEndpointService } from '../../../core/shared/hal-endpoint.service';
import { GetRequest, RestRequest } from '../../../core/data/request.models';
@@ -9,7 +9,6 @@ import { GenericConstructor } from '../../../core/shared/generic-constructor';
import { FilteredDiscoveryPageResponseParsingService } from '../../../core/data/filtered-discovery-page-response-parsing.service';
import { hasValue } from '../../../shared/empty.util';
import { configureRequest, getResponseFromEntry } from '../../../core/shared/operators';
import { RouteService } from '../../../shared/services/route.service';
import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models';
/**
@@ -19,8 +18,7 @@ import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.mod
export class SearchFixedFilterService {
private queryByFilterPath = 'filtered-discovery-pages';
constructor(private routeService: RouteService,
protected requestService: RequestService,
constructor(protected requestService: RequestService,
private halService: HALEndpointService) {
}

View File

@@ -17,7 +17,7 @@
</div>
<ds-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate"
[action]="getCurrentUrl()"
[action]="currentUrl"
[name]="filterConfig.paramName"
[(ngModel)]="filter"
(submitSuggestion)="onSubmit($event)"

View File

@@ -1,7 +1,7 @@
<div>
<div class="filters py-2">
<form #form="ngForm" (ngSubmit)="onSubmit()" class="add-filter row"
[action]="getCurrentUrl()">
[action]="currentUrl">
<div class="col-6">
<input type="text" [(ngModel)]="range[0]" [name]="filterConfig.paramName + '.min'"
class="form-control" (blur)="onSubmit()"

View File

@@ -16,7 +16,7 @@ import { RouterStub } from '../../../../shared/testing/router-stub';
import { Router } from '@angular/router';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { SearchRangeFilterComponent } from './search-range-filter.component';
import { RouteService } from '../../../../shared/services/route.service';
import { RouteService } from '../../../../core/services/route.service';
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service-stub';

View File

@@ -14,7 +14,7 @@ import { FILTER_CONFIG, IN_PLACE_SEARCH, SearchFilterService } from '../search-f
import { SearchService } from '../../../search-service/search.service';
import { Router } from '@angular/router';
import * as moment from 'moment';
import { RouteService } from '../../../../shared/services/route.service';
import { RouteService } from '../../../../core/services/route.service';
import { hasValue } from '../../../../shared/empty.util';
import { SearchConfigurationService } from '../../../search-service/search-configuration.service';
import { SEARCH_CONFIG_SERVICE } from '../../../../+my-dspace-page/my-dspace-page.component';

View File

@@ -17,7 +17,7 @@
</div>
<ds-input-suggestions [suggestions]="(filterSearchResults | async)"
[placeholder]="'search.filters.filter.' + filterConfig.name + '.placeholder'| translate"
[action]="getCurrentUrl()"
[action]="currentUrl"
[name]="filterConfig.paramName"
[(ngModel)]="filter"
(submitSuggestion)="onSubmit($event)"

View File

@@ -4,4 +4,4 @@
<ds-search-filter [filter]="filter" [inPlaceSearch]="inPlaceSearch"></ds-search-filter>
</div>
</div>
<a class="btn btn-primary" [routerLink]="[getSearchLink()]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button">{{"search.filters.reset" | translate}}</a>
<a class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" queryParamsHandling="merge" role="button">{{"search.filters.reset" | translate}}</a>

View File

@@ -58,7 +58,7 @@ describe('SearchFiltersComponent', () => {
describe('when the getSearchLink method is called', () => {
beforeEach(() => {
spyOn(searchService, 'getSearchLink');
comp.getSearchLink();
(comp as any).getSearchLink();
});
it('should call getSearchLink on the searchService', () => {

View File

@@ -37,6 +37,11 @@ export class SearchFiltersComponent implements OnInit {
*/
@Input() inPlaceSearch;
/**
* Link to the search page
*/
searchLink: string;
/**
* Initialize instance variables
* @param {SearchService} searchService
@@ -60,12 +65,13 @@ export class SearchFiltersComponent implements OnInit {
Object.keys(filters).forEach((f) => filters[f] = null);
return filters;
}));
this.searchLink = this.getSearchLink();
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}

View File

@@ -0,0 +1,6 @@
<a class="badge badge-primary mr-1 mb-1 text-capitalize"
[routerLink]="searchLink"
[queryParams]="(removeParameters | async)" queryParamsHandling="merge">
{{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}}
<span> ×</span>
</a>

View File

@@ -0,0 +1,87 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { Observable, of as observableOf } from 'rxjs';
import { Params } from '@angular/router';
import { SearchLabelComponent } from './search-label.component';
import { ObjectKeysPipe } from '../../../shared/utils/object-keys-pipe';
import { SearchService } from '../../search-service/search.service';
import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component';
import { SearchServiceStub } from '../../../shared/testing/search-service-stub';
import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service-stub';
describe('SearchLabelComponent', () => {
let comp: SearchLabelComponent;
let fixture: ComponentFixture<SearchLabelComponent>;
const searchLink = '/search';
let searchService;
const key1 = 'author';
const key2 = 'subject';
const value1 = 'Test, Author';
const normValue1 = 'Test, Author';
const value2 = 'TestSubject';
const value3 = 'Test, Authority,authority';
const normValue3 = 'Test, Authority';
const filter1 = [key1, value1];
const filter2 = [key2, value2];
const mockFilters = [
filter1,
filter2
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
declarations: [SearchLabelComponent, ObjectKeysPipe],
providers: [
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }
// { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(SearchLabelComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchLabelComponent);
comp = fixture.componentInstance;
searchService = (comp as any).searchService;
comp.key = key1;
comp.value = value1;
(comp as any).appliedFilters = observableOf(mockFilters);
fixture.detectChanges();
});
describe('when getRemoveParams is called', () => {
let obs: Observable<Params>;
beforeEach(() => {
obs = comp.getRemoveParams();
});
it('should return all params but the provided filter', () => {
obs.subscribe((params) => {
// Should contain only filter2 and page: length == 2
expect(Object.keys(params).length).toBe(2);
});
})
});
describe('when normalizeFilterValue is called', () => {
it('should return properly filter value', () => {
let result: string;
result = comp.normalizeFilterValue(value1);
expect(result).toBe(normValue1);
result = comp.normalizeFilterValue(value3);
expect(result).toBe(normValue3);
})
});
});

View File

@@ -0,0 +1,75 @@
import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Params } from '@angular/router';
import { SearchService } from '../../search-service/search.service';
import { map } from 'rxjs/operators';
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
@Component({
selector: 'ds-search-label',
templateUrl: './search-label.component.html',
})
/**
* Component that represents the label containing the currently active filters
*/
export class SearchLabelComponent implements OnInit {
@Input() key: string;
@Input() value: string;
@Input() inPlaceSearch: boolean;
@Input() appliedFilters: Observable<Params>;
searchLink: string;
removeParameters: Observable<Params>;
/**
* Initialize the instance variable
*/
constructor(
private searchService: SearchService) {
}
ngOnInit(): void {
this.searchLink = this.getSearchLink();
this.removeParameters = this.getRemoveParams();
}
/**
* Calculates the parameters that should change if a given value for the given filter would be removed from the active filters
* @returns {Observable<Params>} The changed filter parameters
*/
getRemoveParams(): Observable<Params> {
return this.appliedFilters.pipe(
map((filters) => {
const field: string = Object.keys(filters).find((f) => f === this.key);
const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null;
return {
[field]: isNotEmpty(newValues) ? newValues : null,
page: 1
};
})
)
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}
return this.searchService.getSearchLink();
}
/**
* TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
* Strips authority operator from filter value
* e.g. 'test ,authority' => 'test'
*
* @param value
*/
normalizeFilterValue(value: string) {
// const pattern = /,[^,]*$/g;
const pattern = /,authority*$/g;
return value.replace(pattern, '');
}
}

View File

@@ -1,13 +1,7 @@
<div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3">
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)"><!--Do not remove this to prevent uneven spacing
--><a *ngFor="let values of (appliedFilters | async)[key]"
class="badge badge-primary mr-1 mb-1 text-capitalize"
[routerLink]="getSearchLink()"
[queryParams]="(getRemoveParams(key, values) | async)" queryParamsHandling="merge">
{{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(values)}}
<span> ×</span>
</a><!--Do not remove this to prevent uneven spacing
--></ng-container>
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)">
<ds-search-label *ngFor="let value of (appliedFilters | async)[key]" [inPlaceSearch]="inPlaceSearch" [key]="key" [value]="value" [appliedFilters]="appliedFilters"></ds-search-label>
</ng-container>
</div>
</div>

View File

@@ -6,11 +6,9 @@ import { SearchService } from '../search-service/search.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
import { Observable, of as observableOf } from 'rxjs';
import { Params } from '@angular/router';
import { of as observableOf } from 'rxjs';
import { ObjectKeysPipe } from '../../shared/utils/object-keys-pipe';
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service-stub';
describe('SearchLabelsComponent', () => {
let comp: SearchLabelsComponent;
@@ -22,10 +20,7 @@ describe('SearchLabelsComponent', () => {
const field1 = 'author';
const field2 = 'subject';
const value1 = 'Test, Author';
const normValue1 = 'Test, Author';
const value2 = 'TestSubject';
const value3 = 'Test, Authority,authority';
const normValue3 = 'Test, Authority';
const filter1 = [field1, value1];
const filter2 = [field2, value2];
const mockFilters = [
@@ -39,8 +34,7 @@ describe('SearchLabelsComponent', () => {
declarations: [SearchLabelsComponent, ObjectKeysPipe],
providers: [
{ provide: SearchService, useValue: new SearchServiceStub(searchLink) },
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }
// { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} }
{ provide: SEARCH_CONFIG_SERVICE, useValue: { getCurrentFrontendFilters: () => observableOf(mockFilters) } }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(SearchLabelsComponent, {
@@ -56,30 +50,11 @@ describe('SearchLabelsComponent', () => {
fixture.detectChanges();
});
describe('when getRemoveParams is called', () => {
let obs: Observable<Params>;
beforeEach(() => {
obs = comp.getRemoveParams(filter1[0], filter1[1]);
});
describe('when the component has been initialized', () => {
it('should return all params but the provided filter', () => {
obs.subscribe((params) => {
// Should contain only filter2 and page: length == 2
expect(Object.keys(params).length).toBe(2);
comp.appliedFilters.subscribe((filters) => {
expect(filters).toBe(mockFilters);
});
})
});
describe('when normalizeFilterValue is called', () => {
it('should return properly filter value', () => {
let result: string;
result = comp.normalizeFilterValue(value1);
expect(result).toBe(normValue1);
result = comp.normalizeFilterValue(value3);
expect(result).toBe(normValue3);
})
});
});

View File

@@ -1,4 +1,4 @@
import { Component, Inject, Input } from '@angular/core';
import { Component, Inject, Input, OnInit } from '@angular/core';
import { SearchService } from '../search-service/search.service';
import { Observable } from 'rxjs';
import { Params } from '@angular/router';
@@ -31,50 +31,7 @@ export class SearchLabelsComponent {
* Initialize the instance variable
*/
constructor(
private searchService: SearchService,
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService) {
this.appliedFilters = this.searchConfigService.getCurrentFrontendFilters();
}
/**
* Calculates the parameters that should change if a given value for the given filter would be removed from the active filters
* @param {string} filterField The filter field parameter name from which the value should be removed
* @param {string} filterValue The value that is removed for this given filter field
* @returns {Observable<Params>} The changed filter parameters
*/
getRemoveParams(filterField: string, filterValue: string): Observable<Params> {
return this.appliedFilters.pipe(
map((filters) => {
const field: string = Object.keys(filters).find((f) => f === filterField);
const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== filterValue) : null;
return {
[field]: isNotEmpty(newValues) ? newValues : null,
page: 1
};
})
)
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}
return this.searchService.getSearchLink();
}
/**
* TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
* Strips authority operator from filter value
* e.g. 'test ,authority' => 'test'
*
* @param value
*/
normalizeFilterValue(value: string) {
// const pattern = /,[^,]*$/g;
const pattern = /,authority*$/g;
return value.replace(pattern, '');
}
}

View File

@@ -13,4 +13,5 @@ import { ConfigurationSearchPageComponent } from './configuration-search-page.co
])
]
})
export class SearchPageRoutingModule { }
export class SearchPageRoutingModule {
}

View File

@@ -7,7 +7,7 @@
<ds-search-form *ngIf="searchEnabled" id="search-form"
[query]="(searchOptions$ | async)?.query"
[scope]="(searchOptions$ | async)?.scope"
[currentUrl]="getSearchLink()"
[currentUrl]="searchLink"
[scopes]="(scopeListRD$ | async)"
[inPlaceSearch]="inPlaceSearch">
</ds-search-form>
@@ -15,12 +15,12 @@
<div class="row">
<div id="search-body"
class="row-offcanvas row-offcanvas-left"
[@pushInOut]="(isSidebarCollapsed() | async) ? 'collapsed' : 'expanded'">
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
id="search-sidebar-sm"
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
(toggleSidebar)="closeSidebar()"
[ngClass]="{'active': !(isSidebarCollapsed() | async)}">
[ngClass]="{'active': !(isSidebarCollapsed$ | async)}">
</ds-search-sidebar>
<div id="search-content" class="col-12">
<div class="d-block d-md-none search-controls clearfix">

View File

@@ -21,7 +21,7 @@ import { SearchFilterService } from './search-filters/search-filter/search-filte
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { RemoteData } from '../core/data/remote-data';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { RouteService } from '../shared/services/route.service';
import { RouteService } from '../core/services/route.service';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { PaginatedSearchOptions } from './paginated-search-options.model';
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
@@ -201,7 +201,7 @@ describe('SearchPageComponent', () => {
beforeEach(() => {
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
comp.isSidebarCollapsed = () => observableOf(true);
(comp as any).isSidebarCollapsed$ = observableOf(true);
fixture.detectChanges();
});
@@ -216,7 +216,7 @@ describe('SearchPageComponent', () => {
beforeEach(() => {
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
comp.isSidebarCollapsed = () => observableOf(false);
(comp as any).isSidebarCollapsed$ = observableOf(false);
fixture.detectChanges();
});

View File

@@ -13,7 +13,7 @@ import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
import { hasValue, isNotEmpty } from '../shared/empty.util';
import { SearchConfigurationService } from './search-service/search-configuration.service';
import { getSucceededRemoteData } from '../core/shared/operators';
import { RouteService } from '../shared/services/route.service';
import { RouteService } from '../core/services/route.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
export const SEARCH_ROUTE = '/search';
@@ -91,6 +91,16 @@ export class SearchPageComponent implements OnInit {
@Input()
configuration$: Observable<string>;
/**
* Link to the search page
*/
searchLink: string;
/**
* Observable for whether or not the sidebar is currently collapsed
*/
isSidebarCollapsed$: Observable<boolean>;
constructor(protected service: SearchService,
protected sidebarService: SearchSidebarService,
protected windowService: HostWindowService,
@@ -107,6 +117,8 @@ export class SearchPageComponent implements OnInit {
* If something changes, update the list of scopes for the dropdown
*/
ngOnInit(): void {
this.isSidebarCollapsed$ = this.isSidebarCollapsed();
this.searchLink = this.getSearchLink();
this.searchOptions$ = this.getSearchOptions();
this.sub = this.searchOptions$.pipe(
switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(undefined))))
@@ -147,14 +159,14 @@ export class SearchPageComponent implements OnInit {
* Check if the sidebar is collapsed
* @returns {Observable<boolean>} emits true if the sidebar is currently collapsed, false if it is expanded
*/
public isSidebarCollapsed(): Observable<boolean> {
private isSidebarCollapsed(): Observable<boolean> {
return this.sidebarService.isCollapsed;
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
public getSearchLink(): string {
private getSearchLink(): string {
if (this.inPlaceSearch) {
return './';
}

View File

@@ -30,6 +30,7 @@ import { SearchFacetSelectedOptionComponent } from './search-filters/search-filt
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
import { SearchLabelComponent } from './search-labels/search-label/search-label.component';
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
import { FilteredSearchPageComponent } from './filtered-search-page.component';
@@ -50,6 +51,7 @@ const components = [
SearchFilterComponent,
SearchFacetFilterComponent,
SearchLabelsComponent,
SearchLabelComponent,
SearchFacetFilterComponent,
SearchFacetFilterWrapperComponent,
SearchRangeFilterComponent,

View File

@@ -14,7 +14,7 @@ import { SortDirection, SortOptions } from '../../core/cache/models/sort-options
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
import { SearchOptions } from '../search-options.model';
import { PaginatedSearchOptions } from '../paginated-search-options.model';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { RemoteData } from '../../core/data/remote-data';
import { getSucceededRemoteData } from '../../core/shared/operators';

View File

@@ -26,7 +26,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
import { ViewMode } from '../../core/shared/view-mode.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { map } from 'rxjs/operators';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
import { routeServiceStub } from '../../shared/testing/route-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';

View File

@@ -41,7 +41,7 @@ import { Community } from '../../core/shared/community.model';
import { CommunityDataService } from '../../core/data/community-data.service';
import { ViewMode } from '../../core/shared/view-mode.model';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { RouteService } from '../../shared/services/route.service';
import { RouteService } from '../../core/services/route.service';
/**
* Service that performs all general actions that have to do with the search page

View File

@@ -26,7 +26,7 @@ import { HostWindowResizeAction } from './shared/host-window.actions';
import { MetadataService } from './core/metadata/metadata.service';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
import { NativeWindowRef, NativeWindowService } from './shared/services/window.service';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { MockTranslateLoader } from './shared/mocks/mock-translate-loader';
import { MockMetadataService } from './shared/mocks/mock-metadata-service';
@@ -41,9 +41,11 @@ import { MenuServiceStub } from './shared/testing/menu-service-stub';
import { HostWindowService } from './shared/host-window.service';
import { HostWindowServiceStub } from './shared/testing/host-window-service-stub';
import { ActivatedRoute, Router } from '@angular/router';
import { RouteService } from './shared/services/route.service';
import { RouteService } from './core/services/route.service';
import { MockActivatedRoute } from './shared/mocks/mock-active-router';
import { MockRouter } from './shared/mocks/mock-router';
import { MockCookieService } from './shared/mocks/mock-cookie.service';
import { CookieService } from './core/services/cookie.service';
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
@@ -78,6 +80,7 @@ describe('App component', () => {
{ provide: MenuService, useValue: menuService },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
{ provide: CookieService, useValue: new MockCookieService()},
AppComponent,
RouteService
],

View File

@@ -19,11 +19,11 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/host-window.reducer';
import { NativeWindowRef, NativeWindowService } from './shared/services/window.service';
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { isAuthenticated } from './core/auth/selectors';
import { AuthService } from './core/auth/auth.service';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { RouteService } from './shared/services/route.service';
import { RouteService } from './core/services/route.service';
import variables from '../styles/_exposed_variables.scss';
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
import { MenuService } from './shared/menu/menu.service';
@@ -32,6 +32,10 @@ import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
import { slideSidebarPadding } from './shared/animations/slide';
import { HostWindowService } from './shared/host-window.service';
import { Theme } from '../config/theme.inferface';
import { isNotEmpty } from './shared/empty.util';
import { CookieService } from './core/services/cookie.service';
export const LANG_COOKIE = 'language_cookie';
@Component({
selector: 'ds-app',
@@ -61,6 +65,7 @@ export class AppComponent implements OnInit, AfterViewInit {
private cssService: CSSVariableService,
private menuService: MenuService,
private windowService: HostWindowService,
private cookie: CookieService
) {
// Load all the languages that are defined as active from the config file
translate.addLangs(config.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
@@ -68,11 +73,20 @@ export class AppComponent implements OnInit, AfterViewInit {
// Load the default language from the config file
translate.setDefaultLang(config.defaultLanguage);
// Attempt to get the browser language from the user
if (translate.getLangs().includes(translate.getBrowserLang())) {
translate.use(translate.getBrowserLang());
// Attempt to get the language from a cookie
const lang = cookie.get(LANG_COOKIE);
if (isNotEmpty(lang)) {
// Cookie found
// Use the language from the cookie
translate.use(lang);
} else {
translate.use(config.defaultLanguage);
// Cookie not found
// Attempt to get the browser language from the user
if (translate.getLangs().includes(translate.getBrowserLang())) {
translate.use(translate.getBrowserLang());
} else {
translate.use(config.defaultLanguage);
}
}
metadata.listenForRouteChange();

View File

@@ -39,6 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e
import { NavbarModule } from './navbar/navbar.module';
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
import { ClientCookieService } from './core/services/client-cookie.service';
export function getConfig() {
return ENV_CONFIG;
@@ -97,7 +98,8 @@ const PROVIDERS = [
{
provide: RouterStateSerializer,
useClass: DSpaceRouterStateSerializer
}
},
ClientCookieService
];
const DECLARATIONS = [

View File

@@ -7,12 +7,12 @@ import { REQUEST } from '@nguniversal/express-engine/tokens';
import { of as observableOf } from 'rxjs';
import { authReducer, AuthState } from './auth.reducer';
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { AuthService } from './auth.service';
import { RouterStub } from '../../shared/testing/router-stub';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
import { CookieService } from '../../shared/services/cookie.service';
import { CookieService } from '../services/cookie.service';
import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service-stub';
import { AuthRequestService } from './auth-request.service';
import { AuthStatus } from './models/auth-status.model';
@@ -20,7 +20,7 @@ import { AuthTokenInfo } from './models/auth-token-info.model';
import { EPerson } from '../eperson/models/eperson.model';
import { EPersonMock } from '../../shared/testing/eperson-mock';
import { AppState } from '../../app.reducer';
import { ClientCookieService } from '../../shared/services/client-cookie.service';
import { ClientCookieService } from '../services/client-cookie.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';

View File

@@ -15,11 +15,11 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
import { CookieService } from '../../shared/services/cookie.service';
import { CookieService } from '../services/cookie.service';
import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';

View File

@@ -5,7 +5,7 @@ import { AuthEffects } from './auth/auth.effects';
import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects';
import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects';
import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects';
import { RouteEffects } from '../shared/services/route.effects';
import { RouteEffects } from './services/route.effects';
export const coreEffects = [
RequestEffects,

View File

@@ -14,7 +14,7 @@ import { coreReducers } from './core.reducers';
import { isNotEmpty } from '../shared/empty.util';
import { ApiService } from '../shared/services/api.service';
import { ApiService } from './services/api.service';
import { BrowseEntriesResponseParsingService } from './data/browse-entries-response-parsing.service';
import { CollectionDataService } from './data/collection-data.service';
import { CommunityDataService } from './data/community-data.service';
@@ -34,12 +34,12 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
import { RequestService } from './data/request.service';
import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service';
import { ServerResponseService } from '../shared/services/server-response.service';
import { NativeWindowFactory, NativeWindowService } from '../shared/services/window.service';
import { ServerResponseService } from './services/server-response.service';
import { NativeWindowFactory, NativeWindowService } from './services/window.service';
import { BrowseService } from './browse/browse.service';
import { BrowseResponseParsingService } from './data/browse-response-parsing.service';
import { ConfigResponseParsingService } from './config/config-response-parsing.service';
import { RouteService } from '../shared/services/route.service';
import { RouteService } from './services/route.service';
import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service';
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';

View File

@@ -13,7 +13,7 @@ import {
objectUpdatesReducer,
ObjectUpdatesState
} from './data/object-updates/object-updates.reducer';
import { routeReducer, RouteState } from '../shared/services/route.reducer';
import { routeReducer, RouteState } from './services/route.reducer';
export interface CoreState {
'cache/object': ObjectCacheState,

View File

@@ -6,9 +6,9 @@ import { Store } from '@ngrx/store';
import { getTestScheduler, hot } from 'jasmine-marbles';
import { RouteService } from './route.service';
import { MockRouter } from '../mocks/mock-router';
import { MockRouter } from '../../shared/mocks/mock-router';
import { TestScheduler } from 'rxjs/testing';
import { AddUrlToHistoryAction } from '../history/history.actions';
import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
describe('RouteService', () => {
let scheduler: TestScheduler;

View File

@@ -12,12 +12,12 @@ import { combineLatest, Observable } from 'rxjs';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { isEqual } from 'lodash';
import { AddUrlToHistoryAction } from '../history/history.actions';
import { historySelector } from '../history/selectors';
import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
import { historySelector } from '../../shared/history/selectors';
import { SetParametersAction, SetQueryParametersAction } from './route.actions';
import { CoreState } from '../../core/core.reducers';
import { hasValue } from '../empty.util';
import { coreSelector } from '../../core/core.selectors';
import { CoreState } from '../core.reducers';
import { hasValue } from '../../shared/empty.util';
import { coreSelector } from '../core.selectors';
/**
* Selector to select all route parameters from the store

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { JournalGridElementComponent } from './journal-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { OrgunitGridElementComponent } from './orgunit-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -39,7 +42,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { PersonGridElementComponent } from './person-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -33,7 +36,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -3,11 +3,14 @@ import { Item } from '../../../../core/shared/item.model';
import { of as observableOf } from 'rxjs/internal/observable/of';
import { getEntityGridElementTestComponent } from '../../../../shared/object-grid/item-grid-element/item-types/publication/publication-grid-element.component.spec';
import { ProjectGridElementComponent } from './project-grid-element.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -27,7 +30,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -1,4 +1,4 @@
import { ServerResponseService } from '../shared/services/server-response.service';
import { ServerResponseService } from '../core/services/server-response.service';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { AuthService } from '../core/auth/auth.service';

View File

@@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommunityDataService } from '../../../core/data/community-data.service';
import { RouteService } from '../../services/route.service';
import { RouteService } from '../../../core/services/route.service';
import { Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';

View File

@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Community } from '../../../core/shared/community.model';
import { CommunityDataService } from '../../../core/data/community-data.service';
import { Observable } from 'rxjs';
import { RouteService } from '../../services/route.service';
import { RouteService } from '../../../core/services/route.service';
import { Router } from '@angular/router';
import { RemoteData } from '../../../core/data/remote-data';
import { isNotEmpty, isNotUndefined } from '../../empty.util';

View File

@@ -29,7 +29,7 @@ export class DropdownFieldParser extends FieldParser {
const dropdownModel = new DynamicScrollableDropdownModel(dropdownModelConfig, layout);
return dropdownModel;
} else {
throw Error(`Authority name is not available. Please checks form configuration file.`);
throw Error(`Authority name is not available. Please check the form configuration file.`);
}
}
}

View File

@@ -1 +1 @@
<ng-container *ngComponentOutlet="getComponent(); injector: objectInjector;"></ng-container>
<ng-container *ngComponentOutlet="component; injector: objectInjector;"></ng-container>

View File

@@ -1,16 +1,14 @@
import { ItemTypeSwitcherComponent } from './item-type-switcher.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { of as observableOf } from 'rxjs';
import { PageInfo } from '../../../core/shared/page-info.model';
import { Item } from '../../../core/shared/item.model';
import { PaginatedList } from '../../../core/data/paginated-list';
import { RemoteData } from '../../../core/data/remote-data';
import * as decorator from '../item-type-decorator';
import { getComponentByItemType, ItemViewMode } from '../item-type-decorator';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
import createSpy = jasmine.createSpy;
import { createSuccessfulRemoteDataObject$ } from '../../testing/utils';
import createSpy = jasmine.createSpy;
const relationType = 'type';
const mockItem: Item = Object.assign(new Item(), {
@@ -61,7 +59,7 @@ describe('ItemTypeSwitcherComponent', () => {
describe('when calling getComponent', () => {
beforeEach(() => {
comp.getComponent();
(comp as any).getComponent();
});
it('should call getComponentByItemType with parameters type and viewMode', () => {
@@ -79,7 +77,7 @@ describe('ItemTypeSwitcherComponent', () => {
describe('when calling getComponent', () => {
beforeEach(() => {
comp.getComponent();
(comp as any).getComponent();
});
it('should call getComponentByItemType with parameters type, viewMode and representationType', () => {

View File

@@ -32,6 +32,8 @@ export class ItemTypeSwitcherComponent implements OnInit {
*/
objectInjector: Injector;
component: any;
constructor(private injector: Injector) {
}
@@ -40,14 +42,14 @@ export class ItemTypeSwitcherComponent implements OnInit {
providers: [{ provide: ITEM, useFactory: () => this.object, deps:[] }],
parent: this.injector
});
this.component = this.getComponent();
}
/**
* Fetch the component depending on the item's relationship type
* @returns {string}
*/
getComponent(): string {
private getComponent(): string {
if (hasValue((this.object as any).representationType)) {
const metadataRepresentation = this.object as MetadataRepresentation;
return getComponentByItemType(metadataRepresentation.itemType, this.viewMode, metadataRepresentation.representationType);

View File

@@ -4,7 +4,7 @@
</a>
<ul ngbDropdownMenu class="dropdown-menu" aria-labelledby="dropdownLang">
<li class="dropdown-item" #langSelect *ngFor="let lang of translate.getLangs()"
(click)="translate.use(lang)"
(click)="useLang(lang)"
[class.active]="lang === translate.currentLang">
{{ langLabel(lang) }}
</li>

View File

@@ -6,6 +6,9 @@ import {HttpClientTestingModule, HttpTestingController} from '@angular/common/ht
import { GLOBAL_CONFIG } from '../../../config';
import {LangConfig} from '../../../config/lang-config.interface';
import {Observable, of} from 'rxjs';
import { By } from '@angular/platform-browser';
import { MockCookieService } from '../mocks/mock-cookie.service';
import { CookieService } from '../../core/services/cookie.service';
// This test is completely independent from any message catalogs or keys in the codebase
// The translation module is instantiated with these bogus messages that we aren't using anyway.
@@ -28,8 +31,14 @@ class CustomLoader implements TranslateLoader {
/* tslint:enable:quotemark */
/* tslint:enable:object-literal-key-quotes */
let cookie: CookieService;
describe('LangSwitchComponent', () => {
beforeEach(() => {
cookie = Object.assign(new MockCookieService());
});
describe('with English and Deutsch activated, English as default', () => {
let component: LangSwitchComponent;
let fixture: ComponentFixture<LangSwitchComponent>;
@@ -61,7 +70,11 @@ describe('LangSwitchComponent', () => {
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
providers: [
TranslateService,
{ provide: GLOBAL_CONFIG, useValue: mockConfig },
{ provide: CookieService, useValue: cookie }
]
}).compileComponents()
.then(() => {
translate = TestBed.get(TranslateService);
@@ -73,6 +86,7 @@ describe('LangSwitchComponent', () => {
component = fixture.componentInstance;
de = fixture.debugElement;
langSwitchElement = de.nativeElement;
fixture.detectChanges();
});
}));
@@ -93,6 +107,24 @@ describe('LangSwitchComponent', () => {
it('should define the main A HREF in the UI', (() => {
expect(langSwitchElement.querySelector('a')).toBeDefined();
}));
describe('when selecting a language', () => {
beforeEach(() => {
spyOn(translate, 'use');
spyOn(cookie, 'set');
const langItem = fixture.debugElement.query(By.css('.dropdown-item')).nativeElement;
langItem.click();
fixture.detectChanges();
});
it('should translate the app', () => {
expect(translate.use).toHaveBeenCalled();
});
it('should set the client\'s language cookie', () => {
expect(cookie.set).toHaveBeenCalled();
});
});
});
describe('with English as the only active and also default language', () => {
@@ -127,7 +159,11 @@ describe('LangSwitchComponent', () => {
)],
declarations: [LangSwitchComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [TranslateService, {provide: GLOBAL_CONFIG, useValue: mockConfig}]
providers: [
TranslateService,
{ provide: GLOBAL_CONFIG, useValue: mockConfig },
{ provide: CookieService, useValue: cookie }
]
}).compileComponents();
translate = TestBed.get(TranslateService);
translate.addLangs(mockConfig.languages.filter((MyLangConfig) => MyLangConfig.active === true).map((a) => a.code));

View File

@@ -2,6 +2,8 @@ import {Component, Inject, OnInit} from '@angular/core';
import { GLOBAL_CONFIG, GlobalConfig } from '../../../config';
import {TranslateService} from '@ngx-translate/core';
import {LangConfig} from '../../../config/lang-config.interface';
import { LANG_COOKIE } from '../../app.component';
import { CookieService } from '../../core/services/cookie.service';
@Component({
selector: 'ds-lang-switch',
@@ -23,7 +25,8 @@ export class LangSwitchComponent implements OnInit {
constructor(
@Inject(GLOBAL_CONFIG) public config: GlobalConfig,
public translate: TranslateService
public translate: TranslateService,
public cookie: CookieService
) {
}
@@ -46,4 +49,13 @@ export class LangSwitchComponent implements OnInit {
return this.activeLangs.find((MyLangConfig) => MyLangConfig.code === langcode).label;
}
/**
* Switch to a language and store it in a cookie
* @param lang The language to switch to
*/
useLang(lang: string) {
this.translate.use(lang);
this.cookie.set(LANG_COOKIE, lang);
}
}

View File

@@ -0,0 +1,26 @@
/**
* Mock for [[CookieService]]
*/
export class MockCookieService {
cookies: Map<string, string>;
constructor(cookies: Map<string, string> = new Map()) {
this.cookies = cookies;
}
set(name, value) {
this.cookies.set(name, value);
}
get(name) {
return this.cookies.get(name);
}
remove() {
return jasmine.createSpy('remove');
}
getAll() {
return jasmine.createSpy('getAll');
}
}

View File

@@ -8,7 +8,7 @@
(pageSizeChange)="onPageSizeChange($event)"
(sortDirectionChange)="onSortDirectionChange($event)"
(sortFieldChange)="onSortFieldChange($event)"
*ngIf="getViewMode()===viewModeEnum.List">
*ngIf="(currentMode$ | async) === viewModeEnum.List">
</ds-object-list>
<ds-object-grid [config]="config"
@@ -20,14 +20,14 @@
(pageSizeChange)="onPageSizeChange($event)"
(sortDirectionChange)="onSortDirectionChange($event)"
(sortFieldChange)="onSortFieldChange($event)"
*ngIf="getViewMode()===viewModeEnum.Grid">
*ngIf="(currentMode$ | async) === viewModeEnum.Grid">
</ds-object-grid>
<ds-object-detail [config]="config"
[sortConfig]="sortConfig"
[objects]="objects"
[hideGear]="hideGear"
*ngIf="getViewMode()===viewModeEnum.Detail">
*ngIf="(currentMode$ | async) === viewModeEnum.Detail">
</ds-object-detail>

View File

@@ -1,10 +1,8 @@
import { ObjectCollectionComponent } from './object-collection.component';
import { SetViewMode } from '../view-mode';
import { element } from 'protractor';
import { By } from '@angular/platform-browser';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Config } from '../../../config/config.interface';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { of as observableOf } from 'rxjs';
import { RouterStub } from '../testing/router-stub';
@@ -38,14 +36,14 @@ describe('ObjectCollectionComponent', () => {
}));
it('should only show the grid component when the viewmode is set to grid', () => {
objectCollectionComponent.currentMode = SetViewMode.Grid;
objectCollectionComponent.currentMode$ = observableOf(SetViewMode.Grid);
expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeNull();
});
it('should only show the list component when the viewmode is set to list', () => {
objectCollectionComponent.currentMode = SetViewMode.List;
objectCollectionComponent.currentMode$ = observableOf(SetViewMode.List);
expect(fixture.debugElement.query(By.css('ds-object-list'))).toBeDefined();
expect(fixture.debugElement.query(By.css('ds-object-grid'))).toBeNull();

View File

@@ -11,7 +11,7 @@ import {
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { filter, map, startWith } from 'rxjs/operators';
import { RemoteData } from '../../core/data/remote-data';
import { PageInfo } from '../../core/shared/page-info.model';
@@ -19,14 +19,14 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
import { ListableObject } from './shared/listable-object.model';
import { SetViewMode } from '../view-mode';
import { hasValue, isNotEmpty } from '../empty.util';
import { hasValue, isEmpty, isNotEmpty } from '../empty.util';
@Component({
selector: 'ds-viewable-collection',
styleUrls: ['./object-collection.component.scss'],
templateUrl: './object-collection.component.html',
})
export class ObjectCollectionComponent implements OnChanges, OnInit {
export class ObjectCollectionComponent implements OnInit {
@Input() objects: RemoteData<ListableObject[]>;
@Input() config?: PaginationComponentOptions;
@@ -34,7 +34,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
@Input() hasBorder = false;
@Input() hideGear = false;
pageInfo: Observable<PageInfo>;
private sub;
/**
* An event fired when the page is changed.
* Event's payload equals to the newly selected page.
@@ -61,25 +60,17 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
*/
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
data: any = {};
currentMode: SetViewMode = SetViewMode.List;
currentMode$: Observable<SetViewMode>;
viewModeEnum = SetViewMode;
ngOnChanges(changes: SimpleChanges) {
if (changes.objects && !changes.objects.isFirstChange()) {
// this.pageInfo = this.objects.pageInfo;
}
}
ngOnInit(): void {
// this.pageInfo = this.objects.pageInfo;
this.sub = this.route
this.currentMode$ = this.route
.queryParams
.subscribe((params) => {
if (isNotEmpty(params.view)) {
this.currentMode = params.view;
}
});
.pipe(
filter((params) => isNotEmpty(params.view)),
map((params) => params.view),
startWith(SetViewMode.List)
);
}
/**
@@ -96,15 +87,6 @@ export class ObjectCollectionComponent implements OnChanges, OnInit {
private router: Router) {
}
getViewMode(): SetViewMode {
this.route.queryParams.pipe(map((params) => {
if (isNotEmpty(params.view) && hasValue(params.view)) {
this.currentMode = params.view;
}
}));
return this.currentMode;
}
onPageChange(event) {
this.pageChange.emit(event);
}

View File

@@ -1 +1 @@
<ng-container *ngComponentOutlet="getDetailElement(); injector: objectInjector;"></ng-container>
<ng-container *ngComponentOutlet="detailElement; injector: objectInjector;"></ng-container>

View File

@@ -26,6 +26,8 @@ export class WrapperDetailElementComponent implements OnInit {
*/
objectInjector: Injector;
detailElement: any;
/**
* Initialize instance variables
*
@@ -42,13 +44,13 @@ export class WrapperDetailElementComponent implements OnInit {
providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }],
parent: this.injector
});
this.detailElement = this.getDetailElement();
}
/**
* Return class name for the object to inject
*/
getDetailElement(): string {
private getDetailElement(): string {
const f: GenericConstructor<ListableObject> = this.object.constructor as GenericConstructor<ListableObject>;
return rendersDSOType(f, SetViewMode.Detail);
}

View File

@@ -1,5 +1,5 @@
<ds-truncatable [id]="dso.id">
<div class="card" [@focusShadow]="(isCollapsed() | async)?'blur':'focus'">
<div class="card" [@focusShadow]="(isCollapsed$ | async)?'blur':'focus'">
<a [routerLink]="['/items/' + dso.id]" class="card-img-top full-width">
<div>
<ds-grid-thumbnail [thumbnail]="this.item.getThumbnail() | async">

View File

@@ -9,11 +9,14 @@ import { of as observableOf } from 'rxjs/internal/observable/of';
import { ItemSearchResult } from '../../../../object-collection/shared/item-search-result.model';
import { Item } from '../../../../../core/shared/item.model';
import { ITEM } from '../../../../items/switcher/item-type-switcher.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../testing/utils';
import { PaginatedList } from '../../../../../core/data/paginated-list';
import { PageInfo } from '../../../../../core/shared/page-info.model';
const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithMetadata.hitHighlights = {};
mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{
@@ -45,7 +48,7 @@ mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
mockItemWithoutMetadata.hitHighlights = {};
mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
bitstreams: observableOf({}),
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
metadata: {
'dc.title': [
{

View File

@@ -16,11 +16,13 @@ import { hasValue } from '../../empty.util';
export class SearchResultGridElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
dso: K;
isCollapsed$: Observable<boolean>;
public constructor(@Inject('objectElementProvider') public listableObject: ListableObject, protected truncatableService: TruncatableService) {
super(listableObject);
if (hasValue(this.object)) {
this.dso = this.object.indexableObject;
this.isCollapsed$ = this.isCollapsed();
}
}
@@ -44,7 +46,7 @@ export class SearchResultGridElementComponent<T extends SearchResult<K>, K exten
return Metadata.firstValue([this.object.hitHighlights, this.dso.metadata], keyOrKeys);
}
isCollapsed(): Observable<boolean> {
private isCollapsed(): Observable<boolean> {
return this.truncatableService.isCollapsed(this.dso.id);
}

View File

@@ -1 +1 @@
<ng-container *ngComponentOutlet="getGridElement(); injector: objectInjector;"></ng-container>
<ng-container *ngComponentOutlet="gridElement; injector: objectInjector;"></ng-container>

View File

@@ -12,6 +12,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
export class WrapperGridElementComponent implements OnInit {
@Input() object: ListableObject;
objectInjector: Injector;
gridElement: any;
constructor(private injector: Injector) {
}
@@ -21,7 +22,7 @@ export class WrapperGridElementComponent implements OnInit {
providers: [{ provide: 'objectElementProvider', useFactory: () => (this.object), deps:[] }],
parent: this.injector
});
this.gridElement = this.getGridElement();
}
getGridElement(): string {

View File

@@ -8,6 +8,7 @@ import { ListableObject } from '../../object-collection/shared/listable-object.m
import { AbstractListableElementComponent } from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
import { TruncatableService } from '../../truncatable/truncatable.service';
import { Metadata } from '../../../core/shared/metadata.utils';
import { MetadataMap } from '../../../core/shared/metadata.models';
@Component({
selector: 'ds-search-result-list-element',
@@ -16,6 +17,7 @@ import { Metadata } from '../../../core/shared/metadata.utils';
export class SearchResultListElementComponent<T extends SearchResult<K>, K extends DSpaceObject> extends AbstractListableElementComponent<T> {
dso: K;
metadata: MetadataMap;
public constructor(@Inject('objectElementProvider') public listable: ListableObject, protected truncatableService: TruncatableService) {
super(listable);

View File

@@ -1 +1 @@
<ng-container *ngComponentOutlet="getListElement(); injector: objectInjector;"></ng-container>
<ng-container *ngComponentOutlet="listElement; injector: objectInjector;"></ng-container>

Some files were not shown because too many files have changed in this diff Show More