diff --git a/src/app/browse-by/browse-by-guard.spec.ts b/src/app/browse-by/browse-by-guard.spec.ts
index 933c95a3cb..7fca9b15c0 100644
--- a/src/app/browse-by/browse-by-guard.spec.ts
+++ b/src/app/browse-by/browse-by-guard.spec.ts
@@ -2,8 +2,8 @@ import { first } from 'rxjs/operators';
import { BrowseByGuard } from './browse-by-guard';
import { of as observableOf } from 'rxjs';
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 { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
describe('BrowseByGuard', () => {
describe('canActivate', () => {
@@ -18,7 +18,7 @@ describe('BrowseByGuard', () => {
const id = 'author';
const scope = '1234-65487-12354-1235';
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(() => {
dsoService = {
diff --git a/src/app/browse-by/browse-by-routing.module.ts b/src/app/browse-by/browse-by-routing.module.ts
index 65805f3559..bb67dc65ae 100644
--- a/src/app/browse-by/browse-by-routing.module.ts
+++ b/src/app/browse-by/browse-by-routing.module.ts
@@ -5,8 +5,6 @@ import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolv
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
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({
imports: [
@@ -18,13 +16,6 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
menu: DSOEditMenuResolver
},
children: [
- {
- path: 'srsc',
- component: BrowseByTaxonomyPageComponent,
- canActivate: [BrowseByGuard],
- resolve: { breadcrumb: I18nBreadcrumbResolver },
- data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata.srsc' }
- },
{
path: ':id',
component: ThemedBrowseBySwitcherComponent,
diff --git a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts
index ceb4c6a6c6..b59a46cae1 100644
--- a/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts
+++ b/src/app/browse-by/browse-by-switcher/browse-by-decorator.ts
@@ -26,7 +26,7 @@ const map = new Map();
* @param browseByType The type of page
* @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) {
if (hasNoValue(map.get(browseByType))) {
map.set(browseByType, new Map());
diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts
index c2e1c9cb68..c13405dd4d 100644
--- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts
+++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.spec.ts
@@ -3,9 +3,11 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
-import { BrowseDefinition } from '../../core/shared/browse-definition.model';
import { BehaviorSubject } from 'rxjs';
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', () => {
let comp: BrowseBySwitcherComponent;
@@ -13,33 +15,33 @@ describe('BrowseBySwitcherComponent', () => {
const types = [
Object.assign(
- new BrowseDefinition(), {
+ new FlatBrowseDefinition(), {
id: 'title',
dataType: BrowseByDataType.Title,
}
),
Object.assign(
- new BrowseDefinition(), {
+ new FlatBrowseDefinition(), {
id: 'dateissued',
dataType: BrowseByDataType.Date,
metadataKeys: ['dc.date.issued']
}
),
Object.assign(
- new BrowseDefinition(), {
+ new ValueListBrowseDefinition(), {
id: 'author',
dataType: BrowseByDataType.Metadata,
}
),
Object.assign(
- new BrowseDefinition(), {
+ new ValueListBrowseDefinition(), {
id: 'subject',
dataType: BrowseByDataType.Metadata,
}
),
];
- const data = new BehaviorSubject(createDataWithBrowseDefinition(new BrowseDefinition()));
+ const data = new BehaviorSubject(createDataWithBrowseDefinition(new FlatBrowseDefinition()));
const activatedRouteStub = {
data
@@ -70,7 +72,7 @@ describe('BrowseBySwitcherComponent', () => {
comp = fixture.componentInstance;
}));
- types.forEach((type: BrowseDefinition) => {
+ types.forEach((type: NonHierarchicalBrowseDefinition) => {
describe(`when switching to a browse-by page for "${type.id}"`, () => {
beforeEach(() => {
data.next(createDataWithBrowseDefinition(type));
diff --git a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts
index 0d3a35bebf..35e4edf900 100644
--- a/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts
+++ b/src/app/browse-by/browse-by-switcher/browse-by-switcher.component.ts
@@ -31,7 +31,7 @@ export class BrowseBySwitcherComponent implements OnInit {
*/
ngOnInit(): void {
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()))
);
}
diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
index 149e1e6b33..87c7937b1b 100644
--- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
+++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
@@ -6,5 +6,5 @@
(deselect)="onDeselect($event)">
- {{ 'browse.taxonomy.button' | translate }}
+ {{ 'browse.taxonomy.button' | translate }}
diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts
index bc9380d7ad..484992afbf 100644
--- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts
+++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.spec.ts
@@ -4,17 +4,36 @@ import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.compone
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
import { TranslateModule } from '@ngx-translate/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', () => {
let component: BrowseByTaxonomyPageComponent;
let fixture: ComponentFixture;
+ let themeService: ThemeService;
let detail1: VocabularyEntryDetail;
let detail2: VocabularyEntryDetail;
+ const data = new BehaviorSubject(createDataWithBrowseDefinition(new HierarchicalBrowseDefinition()));
+ const activatedRouteStub = {
+ data
+ };
+
beforeEach(async () => {
+ themeService = jasmine.createSpyObj('themeService', {
+ getThemeName: 'dspace',
+ });
+
await TestBed.configureTestingModule({
imports: [ TranslateModule.forRoot() ],
declarations: [ BrowseByTaxonomyPageComponent ],
+ providers: [
+ { provide: ActivatedRoute, useValue: activatedRouteStub },
+ { provide: ThemeService, useValue: themeService },
+ ],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
diff --git a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts
index b132f299d6..d568a97fd7 100644
--- a/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts
+++ b/src/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts
@@ -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 { 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({
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
*/
-export class BrowseByTaxonomyPageComponent implements OnInit {
+export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
/**
* The {@link VocabularyOptions} object
@@ -27,8 +35,48 @@ export class BrowseByTaxonomyPageComponent implements OnInit {
*/
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;
+
+ /**
+ * Subscriptions to track
+ */
+ browseByComponentSubs: Subscription[] = [];
+
+ public constructor( protected route: ActivatedRoute,
+ protected themeService: ThemeService,
+ @Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor) {
+ }
+
+ 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.filterValues = this.selectedItems
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
+ this.updateQueryParams();
}
+ /**
+ * Removes detail from selectedItems and filterValues.
+ *
+ * @param detail VocabularyEntryDetail to be removed
+ */
onDeselect(detail: VocabularyEntryDetail): void {
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry !== detail; });
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());
}
}
diff --git a/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts b/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts
new file mode 100644
index 0000000000..212044b853
--- /dev/null
+++ b/src/app/browse-by/browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component.ts
@@ -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{
+
+ protected getComponentName(): string {
+ return 'BrowseByTaxonomyPageComponent';
+ }
+
+ protected importThemedComponent(themeName: string): Promise {
+ return import(`../../../themes/${themeName}/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component`);
+ }
+
+ protected importUnthemedComponent(): Promise {
+ return import(`./browse-by-taxonomy-page.component`);
+ }
+}
diff --git a/src/app/browse-by/browse-by.module.ts b/src/app/browse-by/browse-by.module.ts
index b5b088dead..c0e2d3f9ff 100644
--- a/src/app/browse-by/browse-by.module.ts
+++ b/src/app/browse-by/browse-by.module.ts
@@ -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 { 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 { ThemedBrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component';
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
import { FormModule } from '../shared/form/form.module';
@@ -19,11 +20,12 @@ const ENTRY_COMPONENTS = [
BrowseByTitlePageComponent,
BrowseByMetadataPageComponent,
BrowseByDatePageComponent,
+ BrowseByTaxonomyPageComponent,
ThemedBrowseByMetadataPageComponent,
ThemedBrowseByDatePageComponent,
ThemedBrowseByTitlePageComponent,
-
+ ThemedBrowseByTaxonomyPageComponent,
];
@NgModule({
@@ -37,7 +39,6 @@ const ENTRY_COMPONENTS = [
declarations: [
BrowseBySwitcherComponent,
ThemedBrowseBySwitcherComponent,
- BrowseByTaxonomyPageComponent,
...ENTRY_COMPONENTS
],
exports: [
diff --git a/src/app/core/browse/browse-definition-data.service.ts b/src/app/core/browse/browse-definition-data.service.ts
index 88d070000e..bc495a51f4 100644
--- a/src/app/core/browse/browse-definition-data.service.ts
+++ b/src/app/core/browse/browse-definition-data.service.ts
@@ -1,20 +1,60 @@
+// eslint-disable-next-line max-classes-per-file
import { Injectable } from '@angular/core';
import { BROWSE_DEFINITION } from '../shared/browse-definition.resource-type';
-import { BrowseDefinition } from '../shared/browse-definition.model';
import { RequestService } from '../data/request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
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 { PaginatedList } from '../data/paginated-list.model';
import { FindListOptions } from '../data/find-list-options.model';
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
import { FindAllData, FindAllDataImpl } from '../data/base/find-all-data';
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 { 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,
+ 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 {
+ createAndSendGetRequest(href$: string | Observable, useCachedVersionIfAvailable: boolean = true) {
+ createAndSendBrowseDefinitionGetRequest(this.requestService, this.responseMsToLive, href$, useCachedVersionIfAvailable);
+ }
+}
/**
* 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)
export class BrowseDefinitionDataService extends IdentifiableDataService implements FindAllData, SearchData {
- private findAllData: FindAllDataImpl;
+ private findAllData: BrowseDefinitionFindAllDataImpl;
private searchData: SearchDataImpl;
constructor(
@@ -35,7 +75,7 @@ export class BrowseDefinitionDataService extends IdentifiableDataService, useCachedVersionIfAvailable: boolean = true) {
+ createAndSendBrowseDefinitionGetRequest(this.requestService, this.responseMsToLive, href$, useCachedVersionIfAvailable);
+ }
}
diff --git a/src/app/core/browse/browse.service.spec.ts b/src/app/core/browse/browse.service.spec.ts
index 46ac8c44f4..0e39e53e43 100644
--- a/src/app/core/browse/browse.service.spec.ts
+++ b/src/app/core/browse/browse.service.spec.ts
@@ -6,13 +6,15 @@ import { getMockRequestService } from '../../shared/mocks/request.service.mock';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { RequestService } from '../data/request.service';
-import { BrowseDefinition } from '../shared/browse-definition.model';
import { BrowseEntrySearchOptions } from './browse-entry-search-options.model';
import { BrowseService } from './browse.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList, getFirstUsedArgumentOfSpyMethod } from '../../shared/testing/utils.test';
import { getMockHrefOnlyDataService } from '../../shared/mocks/href-only-data.service.mock';
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', () => {
let scheduler: TestScheduler;
@@ -23,7 +25,7 @@ describe('BrowseService', () => {
const browsesEndpointURL = 'https://rest.api/browses';
const halService: any = new HALEndpointServiceStub(browsesEndpointURL);
const browseDefinitions = [
- Object.assign(new BrowseDefinition(), {
+ Object.assign(new FlatBrowseDefinition(), {
id: 'date',
metadataBrowse: false,
sortOptions: [
@@ -50,7 +52,7 @@ describe('BrowseService', () => {
items: { href: 'https://rest.api/discover/browses/dateissued/items' }
}
}),
- Object.assign(new BrowseDefinition(), {
+ Object.assign(new ValueListBrowseDefinition(), {
id: 'author',
metadataBrowse: true,
sortOptions: [
@@ -78,7 +80,23 @@ describe('BrowseService', () => {
entries: { href: 'https://rest.api/discover/browses/author/entries' },
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;
diff --git a/src/app/core/browse/browse.service.ts b/src/app/core/browse/browse.service.ts
index 989213a978..b210b34949 100644
--- a/src/app/core/browse/browse.service.ts
+++ b/src/app/core/browse/browse.service.ts
@@ -7,6 +7,7 @@ import { PaginatedList } from '../data/paginated-list.model';
import { RemoteData } from '../data/remote-data';
import { RequestService } from '../data/request.service';
import { BrowseDefinition } from '../shared/browse-definition.model';
+import { FlatBrowseDefinition } from '../shared/flat-browse-definition.model';
import { BrowseEntry } from '../shared/browse-entry.model';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
@@ -240,7 +241,12 @@ export class BrowseService {
getPaginatedListPayload(),
map((browseDefinitions: BrowseDefinition[]) => browseDefinitions
.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);
})
),
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 319b42d58b..834c8c02a7 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -177,6 +177,10 @@ import { IdentifierData } from '../shared/object-list/identifier-data/identifier
import { Subscription } from '../shared/subscriptions/models/subscription.model';
import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service';
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
@@ -333,6 +337,10 @@ export const models =
AuthStatus,
BrowseEntry,
BrowseDefinition,
+ NonHierarchicalBrowseDefinition,
+ FlatBrowseDefinition,
+ ValueListBrowseDefinition,
+ HierarchicalBrowseDefinition,
ClaimedTask,
TaskObject,
PoolTask,
diff --git a/src/app/core/data/browse-response-parsing.service.spec.ts b/src/app/core/data/browse-response-parsing.service.spec.ts
new file mode 100644
index 0000000000..9fa7239ef7
--- /dev/null
+++ b/src/app/core/data/browse-response-parsing.service.spec.ts
@@ -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(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);
+ });
+ });
+});
diff --git a/src/app/core/data/browse-response-parsing.service.ts b/src/app/core/data/browse-response-parsing.service.ts
new file mode 100644
index 0000000000..a568cdb617
--- /dev/null
+++ b/src/app/core/data/browse-response-parsing.service.ts
@@ -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(obj): any {
+ const browseType: string = obj.browseType;
+ if (obj.type === BROWSE_DEFINITION.value && hasValue(browseType)) {
+ let serializer: Serializer;
+ 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.');
+ }
+ }
+}
diff --git a/src/app/core/data/dso-response-parsing.service.ts b/src/app/core/data/dso-response-parsing.service.ts
index fd5a22fae9..74117e79d3 100644
--- a/src/app/core/data/dso-response-parsing.service.ts
+++ b/src/app/core/data/dso-response-parsing.service.ts
@@ -10,6 +10,10 @@ import { hasNoValue, hasValue } from '../../shared/empty.util';
import { DSpaceObject } from '../shared/dspace-object.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()
export class DSOResponseParsingService extends BaseResponseParsingService implements ResponseParsingService {
protected toCache = true;
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index 6ab3f180d3..9809bc0fde 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -11,6 +11,7 @@ import { TaskResponseParsingService } from '../tasks/task-response-parsing.servi
import { ContentSourceResponseParsingService } from './content-source-response-parsing.service';
import { RestRequestWithResponseParser } from './rest-request-with-response-parser.model';
import { DspaceRestResponseParsingService } from './dspace-rest-response-parsing.service';
+import { BrowseResponseParsingService } from './browse-response-parsing.service';
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 {
+ return BrowseResponseParsingService;
+ }
+}
+
export class FindListRequest extends GetRequest {
constructor(
uuid: string,
diff --git a/src/app/core/shared/browse-definition.model.ts b/src/app/core/shared/browse-definition.model.ts
index 863f454422..a5bed53c9f 100644
--- a/src/app/core/shared/browse-definition.model.ts
+++ b/src/app/core/shared/browse-definition.model.ts
@@ -1,50 +1,16 @@
-import { autoserialize, autoserializeAs, deserialize } 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 { autoserialize } from 'cerialize';
import { CacheableObject } from '../cache/cacheable-object.model';
-import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-decorator';
-@typedObject
-export class BrowseDefinition extends CacheableObject {
- static type = BROWSE_DEFINITION;
-
- /**
- * The object type
- */
- @excludeFromEquals
- @autoserialize
- type: ResourceType;
+/**
+ * Base class for BrowseDefinition models
+ */
+export abstract class BrowseDefinition extends CacheableObject {
@autoserialize
id: string;
- @autoserialize
- metadataBrowse: boolean;
-
- @autoserialize
- 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;
- };
+ /**
+ * Get the render type of the BrowseDefinition model
+ */
+ abstract getRenderType(): string;
}
diff --git a/src/app/core/shared/flat-browse-definition.model.ts b/src/app/core/shared/flat-browse-definition.model.ts
new file mode 100644
index 0000000000..308f0c7a5b
--- /dev/null
+++ b/src/app/core/shared/flat-browse-definition.model.ts
@@ -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;
+ }
+}
diff --git a/src/app/core/shared/flat-browse-definition.resource-type.ts b/src/app/core/shared/flat-browse-definition.resource-type.ts
new file mode 100644
index 0000000000..bfb01cd98c
--- /dev/null
+++ b/src/app/core/shared/flat-browse-definition.resource-type.ts
@@ -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');
diff --git a/src/app/core/shared/hierarchical-browse-definition.model.ts b/src/app/core/shared/hierarchical-browse-definition.model.ts
new file mode 100644
index 0000000000..ca3ed7bff0
--- /dev/null
+++ b/src/app/core/shared/hierarchical-browse-definition.model.ts
@@ -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';
+ }
+}
diff --git a/src/app/core/shared/hierarchical-browse-definition.resource-type.ts b/src/app/core/shared/hierarchical-browse-definition.resource-type.ts
new file mode 100644
index 0000000000..df06d67c7a
--- /dev/null
+++ b/src/app/core/shared/hierarchical-browse-definition.resource-type.ts
@@ -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');
diff --git a/src/app/core/shared/non-hierarchical-browse-definition.ts b/src/app/core/shared/non-hierarchical-browse-definition.ts
new file mode 100644
index 0000000000..a4f6df43d9
--- /dev/null
+++ b/src/app/core/shared/non-hierarchical-browse-definition.ts
@@ -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;
+ };
+}
diff --git a/src/app/core/shared/value-list-browse-definition.model.ts b/src/app/core/shared/value-list-browse-definition.model.ts
new file mode 100644
index 0000000000..33cce82cac
--- /dev/null
+++ b/src/app/core/shared/value-list-browse-definition.model.ts
@@ -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;
+ }
+}
diff --git a/src/app/core/shared/value-list-browse-definition.resource-type.ts b/src/app/core/shared/value-list-browse-definition.resource-type.ts
new file mode 100644
index 0000000000..8904dc472f
--- /dev/null
+++ b/src/app/core/shared/value-list-browse-definition.resource-type.ts
@@ -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');
diff --git a/src/app/menu.resolver.ts b/src/app/menu.resolver.ts
index c4819be903..3ec1df85fd 100644
--- a/src/app/menu.resolver.ts
+++ b/src/app/menu.resolver.ts
@@ -137,20 +137,6 @@ export class MenuResolver implements Resolve {
} 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, {
shouldPersistOnRouteChange: true
diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts
index ada9be9d0b..983eace055 100644
--- a/src/app/navbar/navbar.component.spec.ts
+++ b/src/app/navbar/navbar.component.spec.ts
@@ -16,7 +16,6 @@ import { RouterTestingModule } from '@angular/router/testing';
import { BrowseService } from '../core/browse/browse.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
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 { Item } from '../core/shared/item.model';
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 { AuthTokenInfo } from '../core/auth/models/auth-token-info.model';
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 fixture: ComponentFixture;
@@ -66,30 +68,35 @@ describe('NavbarComponent', () => {
beforeEach(waitForAsync(() => {
browseDefinitions = [
Object.assign(
- new BrowseDefinition(), {
+ new FlatBrowseDefinition(), {
id: 'title',
dataType: BrowseByDataType.Title,
}
),
Object.assign(
- new BrowseDefinition(), {
+ new FlatBrowseDefinition(), {
id: 'dateissued',
dataType: BrowseByDataType.Date,
metadataKeys: ['dc.date.issued']
}
),
Object.assign(
- new BrowseDefinition(), {
+ new ValueListBrowseDefinition(), {
id: 'author',
dataType: BrowseByDataType.Metadata,
}
),
Object.assign(
- new BrowseDefinition(), {
+ new ValueListBrowseDefinition(), {
id: 'subject',
dataType: BrowseByDataType.Metadata,
}
),
+ Object.assign(
+ new HierarchicalBrowseDefinition(), {
+ id: 'srsc',
+ }
+ ),
];
initialState = {
core: {
diff --git a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
index 2199de920e..891a825745 100644
--- a/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
+++ b/src/app/shared/form/vocabulary-treeview/vocabulary-treeview.component.ts
@@ -248,7 +248,7 @@ export class VocabularyTreeviewComponent implements OnDestroy, OnInit {
}
/**
- * Method called on entry select
+ * Method called on entry select/deselect
*/
onSelect(item: VocabularyEntryDetail) {
if (!this.selectedItems.includes(item.id)) {
diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.html
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts
new file mode 100644
index 0000000000..34d80a0cb8
--- /dev/null
+++ b/src/themes/custom/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component.ts
@@ -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 {
+}
diff --git a/src/themes/custom/lazy-theme.module.ts b/src/themes/custom/lazy-theme.module.ts
index b1290cc634..a79a063e68 100644
--- a/src/themes/custom/lazy-theme.module.ts
+++ b/src/themes/custom/lazy-theme.module.ts
@@ -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 { 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 { BrowseByTaxonomyPageComponent } from './app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component';
import {
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';
@@ -200,6 +201,7 @@ const DECLARATIONS = [
BrowseByMetadataPageComponent,
BrowseByDatePageComponent,
BrowseByTitlePageComponent,
+ BrowseByTaxonomyPageComponent,
ExternalSourceEntryImportModalComponent,
ResultsBackButtonComponent,
DsoEditMetadataComponent,