mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'w2p-101127_browse-by-controlled-vocabulary' into w2p-101127_browse-by-controlled-vocabulary-7.6.0-next
Conflicts: src/app/browse-by/browse-by-routing.module.ts src/app/browse-by/browse-by.module.ts src/app/core/browse/browse-definition-data.service.ts src/app/core/core.module.ts
This commit is contained in:
@@ -2,8 +2,8 @@ import { first } from 'rxjs/operators';
|
|||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
|
||||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||||
|
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
||||||
|
|
||||||
describe('BrowseByGuard', () => {
|
describe('BrowseByGuard', () => {
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
@@ -18,7 +18,7 @@ describe('BrowseByGuard', () => {
|
|||||||
const id = 'author';
|
const id = 'author';
|
||||||
const scope = '1234-65487-12354-1235';
|
const scope = '1234-65487-12354-1235';
|
||||||
const value = 'Filter';
|
const value = 'Filter';
|
||||||
const browseDefinition = Object.assign(new BrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dsoService = {
|
dsoService = {
|
||||||
|
@@ -5,8 +5,6 @@ import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolv
|
|||||||
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component';
|
|
||||||
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -18,13 +16,6 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
|
|||||||
menu: DSOEditMenuResolver
|
menu: DSOEditMenuResolver
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
path: 'srsc',
|
|
||||||
component: BrowseByTaxonomyPageComponent,
|
|
||||||
canActivate: [BrowseByGuard],
|
|
||||||
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
|
||||||
data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata.srsc' }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: ThemedBrowseBySwitcherComponent,
|
component: ThemedBrowseBySwitcherComponent,
|
||||||
|
@@ -26,7 +26,7 @@ const map = new Map();
|
|||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
* @param theme The optional theme for the component
|
* @param theme The optional theme for the component
|
||||||
*/
|
*/
|
||||||
export function rendersBrowseBy(browseByType: BrowseByDataType, theme = DEFAULT_THEME) {
|
export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) {
|
||||||
return function decorator(component: any) {
|
return function decorator(component: any) {
|
||||||
if (hasNoValue(map.get(browseByType))) {
|
if (hasNoValue(map.get(browseByType))) {
|
||||||
map.set(browseByType, new Map());
|
map.set(browseByType, new Map());
|
||||||
|
@@ -3,9 +3,11 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model';
|
||||||
|
import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model';
|
||||||
|
import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition';
|
||||||
|
|
||||||
describe('BrowseBySwitcherComponent', () => {
|
describe('BrowseBySwitcherComponent', () => {
|
||||||
let comp: BrowseBySwitcherComponent;
|
let comp: BrowseBySwitcherComponent;
|
||||||
@@ -13,33 +15,33 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
|
|
||||||
const types = [
|
const types = [
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new FlatBrowseDefinition(), {
|
||||||
id: 'title',
|
id: 'title',
|
||||||
dataType: BrowseByDataType.Title,
|
dataType: BrowseByDataType.Title,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new FlatBrowseDefinition(), {
|
||||||
id: 'dateissued',
|
id: 'dateissued',
|
||||||
dataType: BrowseByDataType.Date,
|
dataType: BrowseByDataType.Date,
|
||||||
metadataKeys: ['dc.date.issued']
|
metadataKeys: ['dc.date.issued']
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new ValueListBrowseDefinition(), {
|
||||||
id: 'author',
|
id: 'author',
|
||||||
dataType: BrowseByDataType.Metadata,
|
dataType: BrowseByDataType.Metadata,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new ValueListBrowseDefinition(), {
|
||||||
id: 'subject',
|
id: 'subject',
|
||||||
dataType: BrowseByDataType.Metadata,
|
dataType: BrowseByDataType.Metadata,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const data = new BehaviorSubject(createDataWithBrowseDefinition(new BrowseDefinition()));
|
const data = new BehaviorSubject(createDataWithBrowseDefinition(new FlatBrowseDefinition()));
|
||||||
|
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
data
|
data
|
||||||
@@ -70,7 +72,7 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
types.forEach((type: BrowseDefinition) => {
|
types.forEach((type: NonHierarchicalBrowseDefinition) => {
|
||||||
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
data.next(createDataWithBrowseDefinition(type));
|
data.next(createDataWithBrowseDefinition(type));
|
||||||
|
@@ -31,7 +31,7 @@ export class BrowseBySwitcherComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByComponent = this.route.data.pipe(
|
this.browseByComponent = this.route.data.pipe(
|
||||||
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType, this.themeService.getThemeName()))
|
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
(deselect)="onDeselect($event)">
|
(deselect)="onDeselect($event)">
|
||||||
</ds-vocabulary-treeview>
|
</ds-vocabulary-treeview>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-primary" [routerLink]="['/search']" [queryParams]="{ 'f.subject': filterValues }">{{ 'browse.taxonomy.button' | translate }}</a>
|
<a class="btn btn-primary" [routerLink]="['/search']" [queryParams]="queryParams">{{ 'browse.taxonomy.button' | translate }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,17 +4,36 @@ import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.compone
|
|||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { createDataWithBrowseDefinition } from '../browse-by-switcher/browse-by-switcher.component.spec';
|
||||||
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
describe('BrowseByTaxonomyPageComponent', () => {
|
describe('BrowseByTaxonomyPageComponent', () => {
|
||||||
let component: BrowseByTaxonomyPageComponent;
|
let component: BrowseByTaxonomyPageComponent;
|
||||||
let fixture: ComponentFixture<BrowseByTaxonomyPageComponent>;
|
let fixture: ComponentFixture<BrowseByTaxonomyPageComponent>;
|
||||||
|
let themeService: ThemeService;
|
||||||
let detail1: VocabularyEntryDetail;
|
let detail1: VocabularyEntryDetail;
|
||||||
let detail2: VocabularyEntryDetail;
|
let detail2: VocabularyEntryDetail;
|
||||||
|
|
||||||
|
const data = new BehaviorSubject(createDataWithBrowseDefinition(new HierarchicalBrowseDefinition()));
|
||||||
|
const activatedRouteStub = {
|
||||||
|
data
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
themeService = jasmine.createSpyObj('themeService', {
|
||||||
|
getThemeName: 'dspace',
|
||||||
|
});
|
||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ TranslateModule.forRoot() ],
|
imports: [ TranslateModule.forRoot() ],
|
||||||
declarations: [ BrowseByTaxonomyPageComponent ],
|
declarations: [ BrowseByTaxonomyPageComponent ],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
|
{ provide: ThemeService, useValue: themeService },
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -1,6 +1,14 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
||||||
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable, Subscription } from 'rxjs';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import { BROWSE_BY_COMPONENT_FACTORY } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ThemeService } from 'src/app/shared/theme-support/theme.service';
|
||||||
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-taxonomy-page',
|
selector: 'ds-browse-by-taxonomy-page',
|
||||||
@@ -10,7 +18,7 @@ import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models
|
|||||||
/**
|
/**
|
||||||
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
||||||
*/
|
*/
|
||||||
export class BrowseByTaxonomyPageComponent implements OnInit {
|
export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link VocabularyOptions} object
|
* The {@link VocabularyOptions} object
|
||||||
@@ -27,8 +35,48 @@ export class BrowseByTaxonomyPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
filterValues: string[];
|
filterValues: string[];
|
||||||
|
|
||||||
ngOnInit() {
|
/**
|
||||||
this.vocabularyOptions = { name: 'srsc', closed: true };
|
* The facet the use when filtering
|
||||||
|
*/
|
||||||
|
facetType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The used vocabulary
|
||||||
|
*/
|
||||||
|
vocabularyName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parameters used in the URL
|
||||||
|
*/
|
||||||
|
queryParams: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolved browse-by component
|
||||||
|
*/
|
||||||
|
browseByComponent: Observable<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriptions to track
|
||||||
|
*/
|
||||||
|
browseByComponentSubs: Subscription[] = [];
|
||||||
|
|
||||||
|
public constructor( protected route: ActivatedRoute,
|
||||||
|
protected themeService: ThemeService,
|
||||||
|
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor<any>) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.browseByComponent = this.route.data.pipe(
|
||||||
|
map((data: { browseDefinition: BrowseDefinition }) => {
|
||||||
|
this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName());
|
||||||
|
return data.browseDefinition;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.browseByComponentSubs.push(this.browseByComponent.subscribe((browseDefinition: HierarchicalBrowseDefinition) => {
|
||||||
|
this.facetType = browseDefinition.facetType;
|
||||||
|
this.vocabularyName = browseDefinition.vocabulary;
|
||||||
|
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,10 +89,30 @@ export class BrowseByTaxonomyPageComponent implements OnInit {
|
|||||||
this.selectedItems.push(detail);
|
this.selectedItems.push(detail);
|
||||||
this.filterValues = this.selectedItems
|
this.filterValues = this.selectedItems
|
||||||
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
||||||
|
this.updateQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes detail from selectedItems and filterValues.
|
||||||
|
*
|
||||||
|
* @param detail VocabularyEntryDetail to be removed
|
||||||
|
*/
|
||||||
onDeselect(detail: VocabularyEntryDetail): void {
|
onDeselect(detail: VocabularyEntryDetail): void {
|
||||||
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; });
|
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; });
|
||||||
this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; });
|
this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; });
|
||||||
|
this.updateQueryParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates queryParams based on the current facetType and filterValues.
|
||||||
|
*/
|
||||||
|
private updateQueryParams(): void {
|
||||||
|
this.queryParams = {
|
||||||
|
['f.' + this.facetType]: this.filterValues
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.browseByComponentSubs.forEach((sub: Subscription) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-browse-by-taxonomy-page',
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
styleUrls: []
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BrowseByTaxonomyPageComponent
|
||||||
|
*/
|
||||||
|
@rendersBrowseBy('hierarchy')
|
||||||
|
export class ThemedBrowseByTaxonomyPageComponent extends ThemedComponent<BrowseByTaxonomyPageComponent>{
|
||||||
|
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BrowseByTaxonomyPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./browse-by-taxonomy-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module';
|
|||||||
import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component';
|
import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component';
|
||||||
import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component';
|
import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component';
|
||||||
import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component';
|
import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component';
|
||||||
|
import { ThemedBrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component';
|
||||||
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
import { FormModule } from '../shared/form/form.module';
|
import { FormModule } from '../shared/form/form.module';
|
||||||
@@ -19,11 +20,12 @@ const ENTRY_COMPONENTS = [
|
|||||||
BrowseByTitlePageComponent,
|
BrowseByTitlePageComponent,
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
BrowseByDatePageComponent,
|
BrowseByDatePageComponent,
|
||||||
|
BrowseByTaxonomyPageComponent,
|
||||||
|
|
||||||
ThemedBrowseByMetadataPageComponent,
|
ThemedBrowseByMetadataPageComponent,
|
||||||
ThemedBrowseByDatePageComponent,
|
ThemedBrowseByDatePageComponent,
|
||||||
ThemedBrowseByTitlePageComponent,
|
ThemedBrowseByTitlePageComponent,
|
||||||
|
ThemedBrowseByTaxonomyPageComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -37,7 +39,6 @@ const ENTRY_COMPONENTS = [
|
|||||||
declarations: [
|
declarations: [
|
||||||
BrowseBySwitcherComponent,
|
BrowseBySwitcherComponent,
|
||||||
ThemedBrowseBySwitcherComponent,
|
ThemedBrowseBySwitcherComponent,
|
||||||
BrowseByTaxonomyPageComponent,
|
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
@@ -1,20 +1,60 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BROWSE_DEFINITION } from '../shared/browse-definition.resource-type';
|
import { BROWSE_DEFINITION } from '../shared/browse-definition.resource-type';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { FindListOptions } from '../data/find-list-options.model';
|
import { FindListOptions } from '../data/find-list-options.model';
|
||||||
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
|
||||||
import { FindAllData, FindAllDataImpl } from '../data/base/find-all-data';
|
import { FindAllData, FindAllDataImpl } from '../data/base/find-all-data';
|
||||||
import { dataService } from '../data/base/data-service.decorator';
|
import { dataService } from '../data/base/data-service.decorator';
|
||||||
|
import { isNotEmpty, isNotEmptyOperator, hasValue } from '../../shared/empty.util';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { BrowseDefinitionRestRequest } from '../data/request.models';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
||||||
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a GET request for the given href, and send it.
|
||||||
|
* Use a GET request specific for BrowseDefinitions.
|
||||||
|
*/
|
||||||
|
export const createAndSendBrowseDefinitionGetRequest = (requestService: RequestService,
|
||||||
|
responseMsToLive: number,
|
||||||
|
href$: string | Observable<string>,
|
||||||
|
useCachedVersionIfAvailable: boolean = true): void => {
|
||||||
|
if (isNotEmpty(href$)) {
|
||||||
|
if (typeof href$ === 'string') {
|
||||||
|
href$ = observableOf(href$);
|
||||||
|
}
|
||||||
|
|
||||||
|
href$.pipe(
|
||||||
|
isNotEmptyOperator(),
|
||||||
|
take(1)
|
||||||
|
).subscribe((href: string) => {
|
||||||
|
const requestId = requestService.generateRequestId();
|
||||||
|
const request = new BrowseDefinitionRestRequest(requestId, href);
|
||||||
|
if (hasValue(responseMsToLive)) {
|
||||||
|
request.responseMsToLive = responseMsToLive;
|
||||||
|
}
|
||||||
|
requestService.send(request, useCachedVersionIfAvailable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom extension of {@link FindAllDataImpl} to be able to send BrowseDefinitionRestRequests
|
||||||
|
*/
|
||||||
|
class BrowseDefinitionFindAllDataImpl extends FindAllDataImpl<BrowseDefinition> {
|
||||||
|
createAndSendGetRequest(href$: string | Observable<string>, useCachedVersionIfAvailable: boolean = true) {
|
||||||
|
createAndSendBrowseDefinitionGetRequest(this.requestService, this.responseMsToLive, href$, useCachedVersionIfAvailable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data service responsible for retrieving browse definitions from the REST server
|
* Data service responsible for retrieving browse definitions from the REST server
|
||||||
@@ -24,7 +64,7 @@ import { SearchData, SearchDataImpl } from '../data/base/search-data';
|
|||||||
})
|
})
|
||||||
@dataService(BROWSE_DEFINITION)
|
@dataService(BROWSE_DEFINITION)
|
||||||
export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseDefinition> implements FindAllData<BrowseDefinition>, SearchData<BrowseDefinition> {
|
export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseDefinition> implements FindAllData<BrowseDefinition>, SearchData<BrowseDefinition> {
|
||||||
private findAllData: FindAllDataImpl<BrowseDefinition>;
|
private findAllData: BrowseDefinitionFindAllDataImpl;
|
||||||
private searchData: SearchDataImpl<BrowseDefinition>;
|
private searchData: SearchDataImpl<BrowseDefinition>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -35,7 +75,7 @@ export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseD
|
|||||||
) {
|
) {
|
||||||
super('browses', requestService, rdbService, objectCache, halService);
|
super('browses', requestService, rdbService, objectCache, halService);
|
||||||
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
this.findAllData = new FindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
this.findAllData = new BrowseDefinitionFindAllDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,5 +161,8 @@ export class BrowseDefinitionDataService extends IdentifiableDataService<BrowseD
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createAndSendGetRequest(href$: string | Observable<string>, useCachedVersionIfAvailable: boolean = true) {
|
||||||
|
createAndSendBrowseDefinitionGetRequest(this.requestService, this.responseMsToLive, href$, useCachedVersionIfAvailable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,13 +6,15 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
|
|||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
|
||||||
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
|
||||||
import { BrowseService } from './browse.service';
|
import { BrowseService } from './browse.service';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { createPaginatedList, getFirstUsedArgumentOfSpyMethod } from '../../shared/testing/utils.test';
|
import { createPaginatedList, getFirstUsedArgumentOfSpyMethod } from '../../shared/testing/utils.test';
|
||||||
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
|
||||||
import { RequestEntry } from '../data/request-entry.model';
|
import { RequestEntry } from '../data/request-entry.model';
|
||||||
|
import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
|
||||||
|
import { ValueListBrowseDefinition } from '../shared/value-list-browse-definition.model';
|
||||||
|
import { HierarchicalBrowseDefinition } from '../shared/hierarchical-browse-definition.model';
|
||||||
|
|
||||||
describe('BrowseService', () => {
|
describe('BrowseService', () => {
|
||||||
let scheduler: TestScheduler;
|
let scheduler: TestScheduler;
|
||||||
@@ -23,7 +25,7 @@ describe('BrowseService', () => {
|
|||||||
const browsesEndpointURL = 'https://rest.api/browses';
|
const browsesEndpointURL = 'https://rest.api/browses';
|
||||||
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
|
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
|
||||||
const browseDefinitions = [
|
const browseDefinitions = [
|
||||||
Object.assign(new BrowseDefinition(), {
|
Object.assign(new FlatBrowseDefinition(), {
|
||||||
id: 'date',
|
id: 'date',
|
||||||
metadataBrowse: false,
|
metadataBrowse: false,
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
@@ -50,7 +52,7 @@ describe('BrowseService', () => {
|
|||||||
items: { href: 'https://rest.api/discover/browses/dateissued/items' }
|
items: { href: 'https://rest.api/discover/browses/dateissued/items' }
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Object.assign(new BrowseDefinition(), {
|
Object.assign(new ValueListBrowseDefinition(), {
|
||||||
id: 'author',
|
id: 'author',
|
||||||
metadataBrowse: true,
|
metadataBrowse: true,
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
@@ -78,7 +80,23 @@ describe('BrowseService', () => {
|
|||||||
entries: { href: 'https://rest.api/discover/browses/author/entries' },
|
entries: { href: 'https://rest.api/discover/browses/author/entries' },
|
||||||
items: { href: 'https://rest.api/discover/browses/author/items' }
|
items: { href: 'https://rest.api/discover/browses/author/items' }
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
Object.assign(new HierarchicalBrowseDefinition(), {
|
||||||
|
id: 'srsc',
|
||||||
|
browseType: 'hierarchicalBrowse',
|
||||||
|
facetType: 'subject',
|
||||||
|
vocabulary: 'srsc',
|
||||||
|
type: 'browse',
|
||||||
|
metadata: [
|
||||||
|
'dc.subject'
|
||||||
|
],
|
||||||
|
_links: {
|
||||||
|
vocabulary: { 'href': 'https://rest.api/submission/vocabularies/srsc/' },
|
||||||
|
items: { 'href': 'https://rest.api/discover/browses/srsc/items' },
|
||||||
|
entries: { 'href': 'https://rest.api/discover/browses/srsc/entries' },
|
||||||
|
self: { 'href': 'https://rest.api/discover/browses/srsc' }
|
||||||
|
}
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
let browseDefinitionDataService;
|
let browseDefinitionDataService;
|
||||||
|
@@ -7,6 +7,7 @@ import { PaginatedList } from '../data/paginated-list.model';
|
|||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { RequestService } from '../data/request.service';
|
import { RequestService } from '../data/request.service';
|
||||||
import { BrowseDefinition } from '../shared/browse-definition.model';
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
|
import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
|
||||||
import { BrowseEntry } from '../shared/browse-entry.model';
|
import { BrowseEntry } from '../shared/browse-entry.model';
|
||||||
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
import { Item } from '../shared/item.model';
|
import { Item } from '../shared/item.model';
|
||||||
@@ -240,7 +241,12 @@ export class BrowseService {
|
|||||||
getPaginatedListPayload(),
|
getPaginatedListPayload(),
|
||||||
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
|
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
|
||||||
.find((def: BrowseDefinition) => {
|
.find((def: BrowseDefinition) => {
|
||||||
const matchingKeys = def.metadataKeys.find((key: string) => searchKeyArray.indexOf(key) >= 0);
|
let matchingKeys = '';
|
||||||
|
|
||||||
|
if (Array.isArray((def as FlatBrowseDefinition).metadataKeys)) {
|
||||||
|
matchingKeys = (def as FlatBrowseDefinition).metadataKeys.find((key: string) => searchKeyArray.indexOf(key) >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
return isNotEmpty(matchingKeys);
|
return isNotEmpty(matchingKeys);
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
@@ -177,6 +177,10 @@ import { IdentifierData } from '../shared/object-list/identifier-data/identifier
|
|||||||
import { Subscription } from '../shared/subscriptions/models/subscription.model';
|
import { Subscription } from '../shared/subscriptions/models/subscription.model';
|
||||||
import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service';
|
import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service';
|
||||||
import { ItemRequest } from './shared/item-request.model';
|
import { ItemRequest } from './shared/item-request.model';
|
||||||
|
import { HierarchicalBrowseDefinition } from './shared/hierarchical-browse-definition.model';
|
||||||
|
import { FlatBrowseDefinition } from './shared/flat-browse-definition.model';
|
||||||
|
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
|
||||||
|
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -333,6 +337,10 @@ export const models =
|
|||||||
AuthStatus,
|
AuthStatus,
|
||||||
BrowseEntry,
|
BrowseEntry,
|
||||||
BrowseDefinition,
|
BrowseDefinition,
|
||||||
|
NonHierarchicalBrowseDefinition,
|
||||||
|
FlatBrowseDefinition,
|
||||||
|
ValueListBrowseDefinition,
|
||||||
|
HierarchicalBrowseDefinition,
|
||||||
ClaimedTask,
|
ClaimedTask,
|
||||||
TaskObject,
|
TaskObject,
|
||||||
PoolTask,
|
PoolTask,
|
||||||
|
64
src/app/core/data/browse-response-parsing.service.spec.ts
Normal file
64
src/app/core/data/browse-response-parsing.service.spec.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { getMockObjectCacheService } from '../../shared/mocks/object-cache.service.mock';
|
||||||
|
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { HIERARCHICAL_BROWSE_DEFINITION } from '../shared/hierarchical-browse-definition.resource-type';
|
||||||
|
import { FLAT_BROWSE_DEFINITION } from '../shared/flat-browse-definition.resource-type';
|
||||||
|
import { VALUE_LIST_BROWSE_DEFINITION } from '../shared/value-list-browse-definition.resource-type';
|
||||||
|
|
||||||
|
class TestService extends BrowseResponseParsingService {
|
||||||
|
constructor(protected objectCache: ObjectCacheService) {
|
||||||
|
super(objectCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite method to make it public for testing
|
||||||
|
public deserialize<ObjectDomain>(obj): any {
|
||||||
|
return super.deserialize(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BrowseResponseParsingService', () => {
|
||||||
|
let service: TestService;
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new TestService(getMockObjectCacheService());
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
const mockFlatBrowse = {
|
||||||
|
id: 'title',
|
||||||
|
browseType: 'flatBrowse',
|
||||||
|
type: 'browse',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockValueList = {
|
||||||
|
id: 'author',
|
||||||
|
browseType: 'valueList',
|
||||||
|
type: 'browse',
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockHierarchicalBrowse = {
|
||||||
|
id: 'srsc',
|
||||||
|
browseType: 'hierarchicalBrowse',
|
||||||
|
type: 'browse',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('should deserialize flatBrowses correctly', () => {
|
||||||
|
let deserialized = service.deserialize(mockFlatBrowse);
|
||||||
|
expect(deserialized.type).toBe(FLAT_BROWSE_DEFINITION);
|
||||||
|
expect(deserialized.id).toBe(mockFlatBrowse.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deserialize valueList browses correctly', () => {
|
||||||
|
let deserialized = service.deserialize(mockValueList);
|
||||||
|
expect(deserialized.type).toBe(VALUE_LIST_BROWSE_DEFINITION);
|
||||||
|
expect(deserialized.id).toBe(mockValueList.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deserialize hierarchicalBrowses correctly', () => {
|
||||||
|
let deserialized = service.deserialize(mockHierarchicalBrowse);
|
||||||
|
expect(deserialized.type).toBe(HIERARCHICAL_BROWSE_DEFINITION);
|
||||||
|
expect(deserialized.id).toBe(mockHierarchicalBrowse.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
48
src/app/core/data/browse-response-parsing.service.ts
Normal file
48
src/app/core/data/browse-response-parsing.service.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import {
|
||||||
|
HIERARCHICAL_BROWSE_DEFINITION
|
||||||
|
} from '../shared/hierarchical-browse-definition.resource-type';
|
||||||
|
import { FLAT_BROWSE_DEFINITION } from '../shared/flat-browse-definition.resource-type';
|
||||||
|
import { HierarchicalBrowseDefinition } from '../shared/hierarchical-browse-definition.model';
|
||||||
|
import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
|
||||||
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
import { Serializer } from '../serializer';
|
||||||
|
import { BrowseDefinition } from '../shared/browse-definition.model';
|
||||||
|
import { BROWSE_DEFINITION } from '../shared/browse-definition.resource-type';
|
||||||
|
import { ValueListBrowseDefinition } from '../shared/value-list-browse-definition.model';
|
||||||
|
import { VALUE_LIST_BROWSE_DEFINITION } from '../shared/value-list-browse-definition.resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A ResponseParsingService used to parse a REST API response to a BrowseDefinition object
|
||||||
|
*/
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class BrowseResponseParsingService extends DspaceRestResponseParsingService {
|
||||||
|
constructor(
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
) {
|
||||||
|
super(objectCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected deserialize<ObjectDomain>(obj): any {
|
||||||
|
const browseType: string = obj.browseType;
|
||||||
|
if (obj.type === BROWSE_DEFINITION.value && hasValue(browseType)) {
|
||||||
|
let serializer: Serializer<BrowseDefinition>;
|
||||||
|
if (browseType === HIERARCHICAL_BROWSE_DEFINITION.value) {
|
||||||
|
serializer = new this.serializerConstructor(HierarchicalBrowseDefinition);
|
||||||
|
} else if (browseType === FLAT_BROWSE_DEFINITION.value) {
|
||||||
|
serializer = new this.serializerConstructor(FlatBrowseDefinition);
|
||||||
|
} else if (browseType === VALUE_LIST_BROWSE_DEFINITION.value) {
|
||||||
|
serializer = new this.serializerConstructor(ValueListBrowseDefinition);
|
||||||
|
} else {
|
||||||
|
throw new Error('An error occurred while retrieving the browse definitions.');
|
||||||
|
}
|
||||||
|
return serializer.deserialize(obj);
|
||||||
|
} else {
|
||||||
|
throw new Error('An error occurred while retrieving the browse definitions.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,6 +10,10 @@ import { hasNoValue, hasValue } from '../../shared/empty.util';
|
|||||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||||
import { RestRequest } from './rest-request.model';
|
import { RestRequest } from './rest-request.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use DspaceRestResponseParsingService for new code, this is only left to support a
|
||||||
|
* few legacy use cases, and should get removed eventually
|
||||||
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DSOResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
export class DSOResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
|
||||||
protected toCache = true;
|
protected toCache = true;
|
||||||
|
@@ -11,6 +11,7 @@ import { TaskResponseParsingService } from '../tasks/task-response-parsing.servi
|
|||||||
import { ContentSourceResponseParsingService } from './content-source-response-parsing.service';
|
import { ContentSourceResponseParsingService } from './content-source-response-parsing.service';
|
||||||
import { RestRequestWithResponseParser } from './rest-request-with-response-parser.model';
|
import { RestRequestWithResponseParser } from './rest-request-with-response-parser.model';
|
||||||
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
|
||||||
|
import { BrowseResponseParsingService } from './browse-response-parsing.service';
|
||||||
import { FindListOptions } from './find-list-options.model';
|
import { FindListOptions } from './find-list-options.model';
|
||||||
|
|
||||||
|
|
||||||
@@ -118,6 +119,15 @@ export class PatchRequest extends DSpaceRestRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing a BrowseDefinition HTTP Rest request object
|
||||||
|
*/
|
||||||
|
export class BrowseDefinitionRestRequest extends DSpaceRestRequest {
|
||||||
|
getResponseParser(): GenericConstructor<ResponseParsingService> {
|
||||||
|
return BrowseResponseParsingService;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class FindListRequest extends GetRequest {
|
export class FindListRequest extends GetRequest {
|
||||||
constructor(
|
constructor(
|
||||||
uuid: string,
|
uuid: string,
|
||||||
|
@@ -1,50 +1,16 @@
|
|||||||
import { autoserialize, autoserializeAs, deserialize } from 'cerialize';
|
import { autoserialize } from 'cerialize';
|
||||||
import { typedObject } from '../cache/builders/build-decorators';
|
|
||||||
import { excludeFromEquals } from '../utilities/equals.decorators';
|
|
||||||
import { BROWSE_DEFINITION } from './browse-definition.resource-type';
|
|
||||||
import { HALLink } from './hal-link.model';
|
|
||||||
import { ResourceType } from './resource-type';
|
|
||||||
import { SortOption } from './sort-option.model';
|
|
||||||
import { CacheableObject } from '../cache/cacheable-object.model';
|
import { CacheableObject } from '../cache/cacheable-object.model';
|
||||||
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
|
|
||||||
|
|
||||||
@typedObject
|
/**
|
||||||
export class BrowseDefinition extends CacheableObject {
|
* Base class for BrowseDefinition models
|
||||||
static type = BROWSE_DEFINITION;
|
*/
|
||||||
|
export abstract class BrowseDefinition extends CacheableObject {
|
||||||
/**
|
|
||||||
* The object type
|
|
||||||
*/
|
|
||||||
@excludeFromEquals
|
|
||||||
@autoserialize
|
|
||||||
type: ResourceType;
|
|
||||||
|
|
||||||
@autoserialize
|
@autoserialize
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
@autoserialize
|
/**
|
||||||
metadataBrowse: boolean;
|
* Get the render type of the BrowseDefinition model
|
||||||
|
*/
|
||||||
@autoserialize
|
abstract getRenderType(): string;
|
||||||
sortOptions: SortOption[];
|
|
||||||
|
|
||||||
@autoserializeAs('order')
|
|
||||||
defaultSortOrder: string;
|
|
||||||
|
|
||||||
@autoserializeAs('metadata')
|
|
||||||
metadataKeys: string[];
|
|
||||||
|
|
||||||
@autoserialize
|
|
||||||
dataType: BrowseByDataType;
|
|
||||||
|
|
||||||
get self(): string {
|
|
||||||
return this._links.self.href;
|
|
||||||
}
|
|
||||||
|
|
||||||
@deserialize
|
|
||||||
_links: {
|
|
||||||
self: HALLink;
|
|
||||||
entries: HALLink;
|
|
||||||
items: HALLink;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
29
src/app/core/shared/flat-browse-definition.model.ts
Normal file
29
src/app/core/shared/flat-browse-definition.model.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { inheritSerialization } from 'cerialize';
|
||||||
|
import { typedObject } from '../cache/builders/build-decorators';
|
||||||
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
|
import { FLAT_BROWSE_DEFINITION } from './flat-browse-definition.resource-type';
|
||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BrowseDefinition model for browses of type 'flatBrowse'
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
@inheritSerialization(NonHierarchicalBrowseDefinition)
|
||||||
|
export class FlatBrowseDefinition extends NonHierarchicalBrowseDefinition {
|
||||||
|
static type = FLAT_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
type: ResourceType = FLAT_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
get self(): string {
|
||||||
|
return this._links.self.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderType(): string {
|
||||||
|
return this.dataType;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for FlatBrowseDefinition
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const FLAT_BROWSE_DEFINITION = new ResourceType('flatBrowse');
|
47
src/app/core/shared/hierarchical-browse-definition.model.ts
Normal file
47
src/app/core/shared/hierarchical-browse-definition.model.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
|
||||||
|
import { typedObject } from '../cache/builders/build-decorators';
|
||||||
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
|
import { HIERARCHICAL_BROWSE_DEFINITION } from './hierarchical-browse-definition.resource-type';
|
||||||
|
import { HALLink } from './hal-link.model';
|
||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { BrowseDefinition } from './browse-definition.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BrowseDefinition model for browses of type 'hierarchicalBrowse'
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
@inheritSerialization(BrowseDefinition)
|
||||||
|
export class HierarchicalBrowseDefinition extends BrowseDefinition {
|
||||||
|
static type = HIERARCHICAL_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
type: ResourceType = HIERARCHICAL_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
facetType: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
vocabulary: string;
|
||||||
|
|
||||||
|
@autoserializeAs('metadata')
|
||||||
|
metadataKeys: string[];
|
||||||
|
|
||||||
|
get self(): string {
|
||||||
|
return this._links.self.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink;
|
||||||
|
entries: HALLink;
|
||||||
|
items: HALLink;
|
||||||
|
vocabulary: HALLink;
|
||||||
|
};
|
||||||
|
|
||||||
|
getRenderType(): string {
|
||||||
|
return 'hierarchy';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for HierarchicalBrowseDefinition
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const HIERARCHICAL_BROWSE_DEFINITION = new ResourceType('hierarchicalBrowse');
|
32
src/app/core/shared/non-hierarchical-browse-definition.ts
Normal file
32
src/app/core/shared/non-hierarchical-browse-definition.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { autoserialize, autoserializeAs, deserialize, inheritSerialization } from 'cerialize';
|
||||||
|
import { SortOption } from './sort-option.model';
|
||||||
|
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
|
||||||
|
import { HALLink } from './hal-link.model';
|
||||||
|
import { BrowseDefinition } from './browse-definition.model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Super class for NonHierarchicalBrowseDefinition models,
|
||||||
|
* e.g. FlatBrowseDefinition and ValueListBrowseDefinition
|
||||||
|
*/
|
||||||
|
@inheritSerialization(BrowseDefinition)
|
||||||
|
export abstract class NonHierarchicalBrowseDefinition extends BrowseDefinition {
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
sortOptions: SortOption[];
|
||||||
|
|
||||||
|
@autoserializeAs('order')
|
||||||
|
defaultSortOrder: string;
|
||||||
|
|
||||||
|
@autoserializeAs('metadata')
|
||||||
|
metadataKeys: string[];
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
dataType: BrowseByDataType;
|
||||||
|
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink;
|
||||||
|
entries: HALLink;
|
||||||
|
items: HALLink;
|
||||||
|
};
|
||||||
|
}
|
29
src/app/core/shared/value-list-browse-definition.model.ts
Normal file
29
src/app/core/shared/value-list-browse-definition.model.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { inheritSerialization } from 'cerialize';
|
||||||
|
import { typedObject } from '../cache/builders/build-decorators';
|
||||||
|
import { excludeFromEquals } from '../utilities/equals.decorators';
|
||||||
|
import { VALUE_LIST_BROWSE_DEFINITION } from './value-list-browse-definition.resource-type';
|
||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
import { NonHierarchicalBrowseDefinition } from './non-hierarchical-browse-definition';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BrowseDefinition model for browses of type 'valueList'
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
@inheritSerialization(NonHierarchicalBrowseDefinition)
|
||||||
|
export class ValueListBrowseDefinition extends NonHierarchicalBrowseDefinition {
|
||||||
|
static type = VALUE_LIST_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
type: ResourceType = VALUE_LIST_BROWSE_DEFINITION;
|
||||||
|
|
||||||
|
get self(): string {
|
||||||
|
return this._links.self.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRenderType(): string {
|
||||||
|
return this.dataType;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from './resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for ValueListBrowseDefinition
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const VALUE_LIST_BROWSE_DEFINITION = new ResourceType('valueList');
|
@@ -137,20 +137,6 @@ export class MenuResolver implements Resolve<boolean> {
|
|||||||
} as TextMenuItemModel,
|
} as TextMenuItemModel,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
menuList.push(
|
|
||||||
{
|
|
||||||
id: 'browse_global_by_srsc',
|
|
||||||
parentID: 'browse_global',
|
|
||||||
active: false,
|
|
||||||
visible: true,
|
|
||||||
index: 99,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.LINK,
|
|
||||||
text: `menu.section.browse_global_by_srsc`,
|
|
||||||
link: `/browse/srsc`
|
|
||||||
} as LinkMenuItemModel
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, {
|
menuList.forEach((menuSection) => this.menuService.addSection(MenuID.PUBLIC, Object.assign(menuSection, {
|
||||||
shouldPersistOnRouteChange: true
|
shouldPersistOnRouteChange: true
|
||||||
|
@@ -16,7 +16,6 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
import { BrowseService } from '../core/browse/browse.service';
|
import { BrowseService } from '../core/browse/browse.service';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
import { buildPaginatedList } from '../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../core/data/paginated-list.model';
|
||||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
|
||||||
import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType } from '../browse-by/browse-by-switcher/browse-by-decorator';
|
||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
@@ -28,6 +27,9 @@ import { authReducer } from '../core/auth/auth.reducer';
|
|||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
|
import { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
|
||||||
import { EPersonMock } from '../shared/testing/eperson.mock';
|
import { EPersonMock } from '../shared/testing/eperson.mock';
|
||||||
|
import { FlatBrowseDefinition } from '../core/shared/flat-browse-definition.model';
|
||||||
|
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
||||||
|
import { HierarchicalBrowseDefinition } from '../core/shared/hierarchical-browse-definition.model';
|
||||||
|
|
||||||
let comp: NavbarComponent;
|
let comp: NavbarComponent;
|
||||||
let fixture: ComponentFixture<NavbarComponent>;
|
let fixture: ComponentFixture<NavbarComponent>;
|
||||||
@@ -66,30 +68,35 @@ describe('NavbarComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
browseDefinitions = [
|
browseDefinitions = [
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new FlatBrowseDefinition(), {
|
||||||
id: 'title',
|
id: 'title',
|
||||||
dataType: BrowseByDataType.Title,
|
dataType: BrowseByDataType.Title,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new FlatBrowseDefinition(), {
|
||||||
id: 'dateissued',
|
id: 'dateissued',
|
||||||
dataType: BrowseByDataType.Date,
|
dataType: BrowseByDataType.Date,
|
||||||
metadataKeys: ['dc.date.issued']
|
metadataKeys: ['dc.date.issued']
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new ValueListBrowseDefinition(), {
|
||||||
id: 'author',
|
id: 'author',
|
||||||
dataType: BrowseByDataType.Metadata,
|
dataType: BrowseByDataType.Metadata,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Object.assign(
|
Object.assign(
|
||||||
new BrowseDefinition(), {
|
new ValueListBrowseDefinition(), {
|
||||||
id: 'subject',
|
id: 'subject',
|
||||||
dataType: BrowseByDataType.Metadata,
|
dataType: BrowseByDataType.Metadata,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
Object.assign(
|
||||||
|
new HierarchicalBrowseDefinition(), {
|
||||||
|
id: 'srsc',
|
||||||
|
}
|
||||||
|
),
|
||||||
];
|
];
|
||||||
initialState = {
|
initialState = {
|
||||||
core: {
|
core: {
|
||||||
|
@@ -248,7 +248,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called on entry select
|
* Method called on entry select/deselect
|
||||||
*/
|
*/
|
||||||
onSelect(item: VocabularyEntryDetail) {
|
onSelect(item: VocabularyEntryDetail) {
|
||||||
if (!this.selectedItems.includes(item.id)) {
|
if (!this.selectedItems.includes(item.id)) {
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { BrowseByTaxonomyPageComponent as BaseComponent } from '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-taxonomy-page',
|
||||||
|
// templateUrl: './browse-by-taxonomy-page.component.html',
|
||||||
|
templateUrl: '../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html',
|
||||||
|
// styleUrls: ['./browse-by-taxonomy-page.component.scss'],
|
||||||
|
styleUrls: ['../../../../../app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss'],
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
||||||
|
*/
|
||||||
|
export class BrowseByTaxonomyPageComponent extends BaseComponent {
|
||||||
|
}
|
@@ -114,6 +114,7 @@ import { ObjectListComponent } from './app/shared/object-list/object-list.compon
|
|||||||
import { BrowseByMetadataPageComponent } from './app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component';
|
import { BrowseByMetadataPageComponent } from './app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component';
|
||||||
import { BrowseByDatePageComponent } from './app/browse-by/browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDatePageComponent } from './app/browse-by/browse-by-date-page/browse-by-date-page.component';
|
||||||
import { BrowseByTitlePageComponent } from './app/browse-by/browse-by-title-page/browse-by-title-page.component';
|
import { BrowseByTitlePageComponent } from './app/browse-by/browse-by-title-page/browse-by-title-page.component';
|
||||||
|
import { BrowseByTaxonomyPageComponent } from './app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component';
|
||||||
import {
|
import {
|
||||||
ExternalSourceEntryImportModalComponent
|
ExternalSourceEntryImportModalComponent
|
||||||
} from './app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component';
|
} from './app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/external-source-entry-import-modal/external-source-entry-import-modal.component';
|
||||||
@@ -200,6 +201,7 @@ const DECLARATIONS = [
|
|||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
BrowseByDatePageComponent,
|
BrowseByDatePageComponent,
|
||||||
BrowseByTitlePageComponent,
|
BrowseByTitlePageComponent,
|
||||||
|
BrowseByTaxonomyPageComponent,
|
||||||
ExternalSourceEntryImportModalComponent,
|
ExternalSourceEntryImportModalComponent,
|
||||||
ResultsBackButtonComponent,
|
ResultsBackButtonComponent,
|
||||||
DsoEditMetadataComponent,
|
DsoEditMetadataComponent,
|
||||||
|
Reference in New Issue
Block a user