From ac58297cbf1d5d5b3d912a0b2c8cd30938773b13 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 13 Nov 2019 17:23:28 +0100 Subject: [PATCH 01/46] 66156: Tabbed related entities search --- resources/i18n/en.json5 | 4 +++ src/app/+item-page/item-page.module.ts | 7 ++-- ...bed-related-entities-search.component.html | 13 +++++++ ...abbed-related-entities-search.component.ts | 36 +++++++++++++++++++ .../item-pages/journal/journal.component.html | 6 ++-- .../item-pages/person/person.component.html | 6 ++-- .../item-pages/journal/journal.component.html | 6 ++-- .../item-pages/person/person.component.html | 6 ++-- 8 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index dfcb397f4f..b088ce5545 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -405,6 +405,10 @@ "item.page.link.full": "Full item page", "item.page.link.simple": "Simple item page", "item.page.person.search.title": "Articles by this author", + + "item.page.relationships.isAuthorOfPublication": "Publications", + "item.page.relationships.isJournalOfPublication": "Publications", + "item.page.subject": "Keywords", "item.page.uri": "URI", diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts index 2a5d0b6da7..b4705f1e7c 100644 --- a/src/app/+item-page/item-page.module.ts +++ b/src/app/+item-page/item-page.module.ts @@ -26,6 +26,7 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component'; import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component'; import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component'; +import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component'; @NgModule({ imports: [ @@ -53,7 +54,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field ItemComponent, GenericItemPageFieldComponent, MetadataRepresentationListComponent, - RelatedEntitiesSearchComponent + RelatedEntitiesSearchComponent, + TabbedRelatedEntitiesSearchComponent ], exports: [ ItemComponent, @@ -63,7 +65,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field RelatedEntitiesSearchComponent, RelatedItemsComponent, MetadataRepresentationListComponent, - ItemPageTitleFieldComponent + ItemPageTitleFieldComponent, + TabbedRelatedEntitiesSearchComponent ], entryComponents: [ PublicationComponent diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html new file mode 100644 index 0000000000..ea142a2337 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -0,0 +1,13 @@ + + + +
+ + +
+
+
+
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts new file mode 100644 index 0000000000..c424f2d765 --- /dev/null +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -0,0 +1,36 @@ +import { Component, Input } from '@angular/core'; +import { Item } from '../../../../core/shared/item.model'; + +@Component({ + selector: 'ds-tabbed-related-entities-search', + templateUrl: './tabbed-related-entities-search.component.html' +}) +/** + * A component to show related items as search results, split into tabs by relationship-type + * Related items can be facetted, or queried using an + * optional search box. + */ +export class TabbedRelatedEntitiesSearchComponent { + /** + * The types of relationships to fetch items for + * e.g. 'isAuthorOfPublication' + */ + @Input() relationTypes: string[]; + + /** + * The item to render relationships for + */ + @Input() item: Item; + + /** + * Whether or not the search bar and title should be displayed (defaults to true) + * @type {boolean} + */ + @Input() searchEnabled = true; + + /** + * The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4) + * @type {number} + */ + @Input() sideBarWidth = 4; +} diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index a82d3c5df6..1f3fb2f159 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -35,8 +35,8 @@
- - + +
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 04d7b9e062..560f470ce5 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -51,8 +51,8 @@
- - + +
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index ef827af590..8d79669419 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -61,7 +61,7 @@

{{"item.page.journal.search.title" | translate}}

- - + + diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index 54d7962b97..88f0478a9a 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -79,7 +79,7 @@

{{"item.page.person.search.title" | translate}}

- - + + From 87d468f9b952dd1ac2b51ac45a9fda99b7f283c2 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Tue, 19 Nov 2019 14:42:58 +0100 Subject: [PATCH 02/46] 66156: Hide tabbox on single tab, merged filtered- and configuration-search-page into one component and added configuration option to tabs --- .../related-entities-search.component.html | 5 +- .../related-entities-search.component.ts | 16 ++-- ...bed-related-entities-search.component.html | 15 +++- ...abbed-related-entities-search.component.ts | 6 +- .../configuration-search-page.component.ts | 12 ++- .../filtered-search-page.component.spec.ts | 21 ------ .../filtered-search-page.component.ts | 73 ------------------- src/app/+search-page/search-page.module.ts | 2 - .../item-pages/journal/journal.component.html | 5 +- .../item-pages/person/person.component.html | 5 +- .../item-pages/journal/journal.component.html | 5 +- .../item-pages/person/person.component.html | 5 +- 12 files changed, 54 insertions(+), 116 deletions(-) delete mode 100644 src/app/+search-page/filtered-search-page.component.spec.ts delete mode 100644 src/app/+search-page/filtered-search-page.component.ts diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html index c7e1f77264..75f3b7aaad 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.html @@ -1,6 +1,7 @@ - - + diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts index 4c0b127925..8f65cb9858 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.ts @@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit { */ @Input() relationType: string; + /** + * An optional configuration to use for the search options + */ + @Input() configuration: string; + /** * The item to render relationships for */ @Input() item: Item; - /** - * The entity type of the relationship items to be displayed - * e.g. 'publication' - * This determines the title of the search results (if search is enabled) - */ - @Input() relationEntityType: string; - /** * Whether or not the search bar and title should be displayed (defaults to true) * @type {boolean} @@ -56,8 +54,8 @@ export class RelatedEntitiesSearchComponent implements OnInit { if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) { this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id); } - if (isNotEmpty(this.relationEntityType)) { - this.configuration$ = of(this.relationEntityType); + if (isNotEmpty(this.configuration)) { + this.configuration$ = of(this.configuration); } } diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index ea142a2337..69670d8f51 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,9 +1,10 @@ - - + +
@@ -11,3 +12,11 @@ +
+ + +
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index c424f2d765..3b46300267 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -15,7 +15,11 @@ export class TabbedRelatedEntitiesSearchComponent { * The types of relationships to fetch items for * e.g. 'isAuthorOfPublication' */ - @Input() relationTypes: string[]; + @Input() relationTypes: Array<{ + label: string, + filter: string, + configuration?: string + }>; /** * The item to render relationships for diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts index b1a94fc086..b36353584c 100644 --- a/src/app/+search-page/configuration-search-page.component.ts +++ b/src/app/+search-page/configuration-search-page.component.ts @@ -35,6 +35,12 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem */ @Input() configuration: string; + /** + * The actual query for the fixed filter. + * If empty, the query will be determined by the route parameter called 'filter' + */ + @Input() fixedFilterQuery: string; + constructor(protected service: SearchService, protected sidebarService: SearchSidebarService, protected windowService: HostWindowService, @@ -64,7 +70,11 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem return this.searchConfigService.paginatedSearchOptions.pipe( map((options: PaginatedSearchOptions) => { const config = this.configuration || options.configuration; - return Object.assign(options, { configuration: config }); + const filter = this.fixedFilterQuery || options.fixedFilter; + return Object.assign(options, { + configuration: config, + fixedFilter: filter + }); }) ); } diff --git a/src/app/+search-page/filtered-search-page.component.spec.ts b/src/app/+search-page/filtered-search-page.component.spec.ts deleted file mode 100644 index 59ab9d7b0d..0000000000 --- a/src/app/+search-page/filtered-search-page.component.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FilteredSearchPageComponent } from './filtered-search-page.component'; -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { configureSearchComponentTestingModule } from './search-page.component.spec'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; - -describe('FilteredSearchPageComponent', () => { - let comp: FilteredSearchPageComponent; - let fixture: ComponentFixture; - let searchConfigService: SearchConfigurationService; - - beforeEach(async(() => { - configureSearchComponentTestingModule(FilteredSearchPageComponent); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(FilteredSearchPageComponent); - comp = fixture.componentInstance; - searchConfigService = (comp as any).searchConfigService; - fixture.detectChanges(); - }); -}); diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts deleted file mode 100644 index 0bcc9e14e3..0000000000 --- a/src/app/+search-page/filtered-search-page.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { HostWindowService } from '../shared/host-window.service'; -import { SearchService } from './search-service/search.service'; -import { SearchSidebarService } from './search-sidebar/search-sidebar.service'; -import { SearchPageComponent } from './search-page.component'; -import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core'; -import { pushInOut } from '../shared/animations/push'; -import { SearchConfigurationService } from './search-service/search-configuration.service'; -import { Observable } from 'rxjs'; -import { PaginatedSearchOptions } from './paginated-search-options.model'; -import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component'; -import { map } from 'rxjs/operators'; -import { RouteService } from '../core/services/route.service'; - -/** - * This component renders a simple item page. - * The route parameter 'id' is used to request the item it represents. - * All fields of the item that should be displayed, are defined in its template. - */ -@Component({ - selector: 'ds-filtered-search-page', - styleUrls: ['./search-page.component.scss'], - templateUrl: './search-page.component.html', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [pushInOut], - providers: [ - { - provide: SEARCH_CONFIG_SERVICE, - useClass: SearchConfigurationService - } - ] -}) - -export class FilteredSearchPageComponent extends SearchPageComponent implements OnInit { - /** - * The actual query for the fixed filter. - * If empty, the query will be determined by the route parameter called 'filter' - */ - @Input() fixedFilterQuery: string; - - constructor(protected service: SearchService, - protected sidebarService: SearchSidebarService, - protected windowService: HostWindowService, - @Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService, - protected routeService: RouteService) { - super(service, sidebarService, windowService, searchConfigService, routeService); - } - - /** - * Listening to changes in the paginated search options - * If something changes, update the search results - * - * Listen to changes in the scope - * If something changes, update the list of scopes for the dropdown - */ - ngOnInit(): void { - super.ngOnInit(); - } - - /** - * Get the current paginated search options after updating the fixed filter using the fixedFilterQuery input - * This is to make sure the fixed filter is included in the paginated search options, as it is not part of any - * query or route parameters - * @returns {Observable} - */ - protected getSearchOptions(): Observable { - return this.searchConfigService.paginatedSearchOptions.pipe( - map((options: PaginatedSearchOptions) => { - const filter = this.fixedFilterQuery || options.fixedFilter; - return Object.assign(options, { fixedFilter: filter }); - }) - ); - } -} diff --git a/src/app/+search-page/search-page.module.ts b/src/app/+search-page/search-page.module.ts index f4c665d06e..efc647f086 100644 --- a/src/app/+search-page/search-page.module.ts +++ b/src/app/+search-page/search-page.module.ts @@ -32,7 +32,6 @@ import { SearchAuthorityFilterComponent } from './search-filters/search-filter/s import { SearchLabelComponent } from './search-labels/search-label/search-label.component'; import { ConfigurationSearchPageComponent } from './configuration-search-page.component'; import { ConfigurationSearchPageGuard } from './configuration-search-page.guard'; -import { FilteredSearchPageComponent } from './filtered-search-page.component'; const effects = [ SearchSidebarEffects @@ -59,7 +58,6 @@ const components = [ SearchFacetRangeOptionComponent, SearchSwitchConfigurationComponent, SearchAuthorityFilterComponent, - FilteredSearchPageComponent, ConfigurationSearchPageComponent ]; diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 562d4b44fc..e86ab35e0e 100644 --- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -37,7 +37,10 @@
+ [relationTypes]="[{ + label: 'isJournalOfPublication', + filter: 'isJournalOfPublication' + }]">
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index 538dd4a241..97a3cf416e 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -54,7 +54,10 @@
+ [relationTypes]="[{ + label: 'isAuthorOfPublication', + filter: 'isAuthorOfPublication' + }]">
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html index 2ad6cbb0ff..089511804e 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html @@ -62,6 +62,9 @@

{{"item.page.journal.search.title" | translate}}

+ [relationTypes]="[{ + label: 'isJournalOfPublication', + filter: 'isJournalOfPublication' + }]"> diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index 6f22006164..1679f9354d 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -80,6 +80,9 @@

{{"item.page.person.search.title" | translate}}

+ [relationTypes]="[{ + label: 'isAuthorOfPublication', + filter: 'isAuthorOfPublication' + }]"> From 63257eac3d766f6e734b3ae83ae8af33128c6fac Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 21 Nov 2019 12:45:38 +0100 Subject: [PATCH 03/46] 66156: AoT build error fix --- .../related-entities-search.component.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts index 711e1b9d3d..7eeddf8e70 100644 --- a/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts +++ b/src/app/+item-page/simple/related-entities/related-entities-search/related-entities-search.component.spec.ts @@ -16,7 +16,7 @@ describe('RelatedEntitiesSearchComponent', () => { id: 'id1' }); const mockRelationType = 'publicationsOfAuthor'; - const mockRelationEntityType = 'publication'; + const mockConfiguration = 'publication'; const mockFilter= `f.${mockRelationType}=${mockItem.id}`; const fixedFilterServiceStub = { getFilterByRelation: () => mockFilter @@ -39,7 +39,7 @@ describe('RelatedEntitiesSearchComponent', () => { fixedFilterService = (comp as any).fixedFilterService; comp.relationType = mockRelationType; comp.item = mockItem; - comp.relationEntityType = mockRelationEntityType; + comp.configuration = mockConfiguration; fixture.detectChanges(); }); @@ -49,7 +49,7 @@ describe('RelatedEntitiesSearchComponent', () => { it('should create a configuration$', () => { comp.configuration$.subscribe((configuration) => { - expect(configuration).toEqual(mockRelationEntityType); + expect(configuration).toEqual(mockConfiguration); }) }); From 64c9d009a99a133bcbcd7c5e79c48386c6ad2a7e Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Thu, 21 Nov 2019 13:28:33 +0100 Subject: [PATCH 04/46] 66156: Active tab in URL for tabbed related entities search --- ...bed-related-entities-search.component.html | 4 +- ...-related-entities-search.component.spec.ts | 83 +++++++++++++++++++ ...abbed-related-entities-search.component.ts | 36 +++++++- 3 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index 69670d8f51..d3690c74be 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,5 +1,5 @@ - - + +
{ + let comp: TabbedRelatedEntitiesSearchComponent; + let fixture: ComponentFixture; + + const mockItem = Object.assign(new Item(), { + id: 'id1' + }); + const mockRelationType = 'publications'; + const relationTypes = [ + { + label: mockRelationType, + filter: mockRelationType + } + ]; + + const router = new MockRouter(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), NoopAnimationsModule, NgbModule.forRoot()], + declarations: [TabbedRelatedEntitiesSearchComponent, VarDirective], + providers: [ + { + provide: ActivatedRoute, + useValue: { + snapshot: { + queryParams: { + tab: mockRelationType + } + } + }, + }, + { provide: Router, useValue: router } + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TabbedRelatedEntitiesSearchComponent); + comp = fixture.componentInstance; + comp.item = mockItem; + comp.relationTypes = relationTypes; + fixture.detectChanges(); + }); + + it('should initialize the activeTab depending on the current query parameters', () => { + expect(comp.activeTab).toEqual(mockRelationType); + }); + + describe('onTabChange', () => { + const event = { + currentId: mockRelationType, + nextId: 'nextTab' + }; + + beforeEach(() => { + comp.onTabChange(event); + }); + + it('should call router natigate with the correct arguments', () => { + expect(router.navigate).toHaveBeenCalledWith([], { + relativeTo: (comp as any).route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + }); + }); + +}); diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index 3b46300267..9fc4e7ec34 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -1,5 +1,6 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; +import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'ds-tabbed-related-entities-search', @@ -10,7 +11,7 @@ import { Item } from '../../../../core/shared/item.model'; * Related items can be facetted, or queried using an * optional search box. */ -export class TabbedRelatedEntitiesSearchComponent { +export class TabbedRelatedEntitiesSearchComponent implements OnInit { /** * The types of relationships to fetch items for * e.g. 'isAuthorOfPublication' @@ -37,4 +38,35 @@ export class TabbedRelatedEntitiesSearchComponent { * @type {number} */ @Input() sideBarWidth = 4; + + /** + * The active tab + */ + activeTab: string; + + constructor(private route: ActivatedRoute, + private router: Router) { + } + + /** + * If the url contains a "tab" query parameter, set this tab to be the active tab + */ + ngOnInit(): void { + this.activeTab = this.route.snapshot.queryParams.tab; + } + + /** + * Add a "tab" query parameter to the URL when changing tabs + * @param event + */ + onTabChange(event) { + this.router.navigate([], { + relativeTo: this.route, + queryParams: { + tab: event.nextId + }, + queryParamsHandling: 'merge' + }); + } + } From b34e6363791fe44f7c5e4eea2c040a2c8f659e73 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 22 Nov 2019 18:14:02 +0100 Subject: [PATCH 05/46] Add support for locally hosted fonts --- webpack/webpack.common.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 028815d958..806c0c6f40 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -15,6 +15,9 @@ module.exports = (env) => { let copyWebpackOptions = [{ from: path.join(__dirname, '..', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'), to: path.join('assets', 'fonts') + }, { + from: path.join(__dirname, '..', 'resources', 'fonts'), + to: path.join('assets', 'fonts') }, { from: path.join(__dirname, '..', 'resources', 'images'), to: path.join('assets', 'images') @@ -24,6 +27,15 @@ module.exports = (env) => { } ]; + const themeFonts = path.join(themePath, 'resources', 'fonts'); + if(theme && fs.existsSync(themeFonts)) { + copyWebpackOptions.push({ + from: themeFonts, + to: path.join('assets', 'fonts') , + force: true, + }); + } + const themeImages = path.join(themePath, 'resources', 'images'); if(theme && fs.existsSync(themeImages)) { copyWebpackOptions.push({ @@ -161,7 +173,7 @@ module.exports = (env) => { ] }, { - test: /\.html$/, + test: /\.(html|eot|ttf|svg|woff|woff2)$/, loader: 'raw-loader' } ] From 29c968250835c51b4c1b6f0e5f600257bfcaa3cb Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Fri, 22 Nov 2019 18:14:36 +0100 Subject: [PATCH 06/46] Fix variable name --- .../item-pages/journal-volume/journal-volume.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index 99d92d2af8..907f70b941 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -4,7 +4,7 @@
- +
Date: Mon, 25 Nov 2019 14:18:21 +0100 Subject: [PATCH 07/46] 66156: Persons and Projects search tabs on OrgUnit pages + mantis fix --- resources/i18n/en.json5 | 6 ++++++ .../item-pages/org-unit/org-unit.component.html | 14 ++++++++++++++ .../org-unit.component.html} | 17 +++++++++++++++++ .../org-unit.component.scss} | 2 +- 4 files changed, 38 insertions(+), 1 deletion(-) rename themes/mantis/app/entity-groups/research-entities/item-pages/{orgunit/orgunit.component.html => org-unit/org-unit.component.html} (81%) rename themes/mantis/app/entity-groups/research-entities/item-pages/{orgunit/orgunit.component.scss => org-unit/org-unit.component.scss} (86%) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 80a7648476..da58d2010c 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -822,6 +822,10 @@ "item.page.relationships.isJournalOfPublication": "Publications", + "item.page.relationships.isOrgUnitOfPerson": "Persons", + + "item.page.relationships.isOrgUnitOfProject": "Projects", + "item.page.subject": "Keywords", "item.page.uri": "URI", @@ -1272,6 +1276,8 @@ "project.page.titleprefix": "Research Project: ", + "project.search.results.head": "Project Search Results", + "publication.listelement.badge": "Publication", diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 4d97868b58..c9227338eb 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -49,4 +49,18 @@
+
+ + +
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html similarity index 81% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 15529a1bd5..4603b8d327 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -74,3 +74,20 @@ +
+
+ + +
+
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss similarity index 86% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss index 54651aede0..4a1d2516da 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; :host { > * { From 4feba32157aefb75327d3dcc51d54361912b3b47 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 9 Oct 2019 18:06:51 +0200 Subject: [PATCH 08/46] 65528: ComCol Tree with full FlatList that gets generated at page-load --- package.json | 1 + resources/i18n/en.json5 | 6 ++ src/app/app-routing.module.ts | 1 + .../CommunityListDataSource.ts | 74 +++++++++++++++++++ .../CommunityListService.ts | 24 ++++++ .../community-list-page.component.html | 2 + .../community-list-page.component.spec.ts | 25 +++++++ .../community-list-page.component.ts | 15 ++++ .../community-list-page.module.ts | 23 ++++++ .../community-list-page.routing.module.ts | 17 +++++ .../community-list.component.html | 25 +++++++ .../community-list.component.spec.ts | 25 +++++++ .../community-list.component.ts | 37 ++++++++++ yarn.lock | 12 +++ 14 files changed, 287 insertions(+) create mode 100644 src/app/community-list-page/CommunityListDataSource.ts create mode 100644 src/app/community-list-page/CommunityListService.ts create mode 100644 src/app/community-list-page/community-list-page.component.html create mode 100644 src/app/community-list-page/community-list-page.component.spec.ts create mode 100644 src/app/community-list-page/community-list-page.component.ts create mode 100644 src/app/community-list-page/community-list-page.module.ts create mode 100644 src/app/community-list-page/community-list-page.routing.module.ts create mode 100644 src/app/community-list-page/community-list/community-list.component.html create mode 100644 src/app/community-list-page/community-list/community-list.component.spec.ts create mode 100644 src/app/community-list-page/community-list/community-list.component.ts diff --git a/package.json b/package.json index 3a54b941dd..80df678e38 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ }, "dependencies": { "@angular/animations": "^6.1.4", + "@angular/cdk": "^6.4.7", "@angular/cli": "^6.1.5", "@angular/common": "^6.1.4", "@angular/core": "^6.1.4", diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 53db8e98c0..144b3128c7 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -340,6 +340,12 @@ + "communityList.tabTitle": "DSpace - Community List", + + "communityList.title": "List of Communities", + + + "community.create.head": "Create a Community", "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 5085633a5b..bd29db4ab8 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -27,6 +27,7 @@ export function getAdminModulePath() { RouterModule.forRoot([ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' }, + { path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' }, { path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, { path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' }, { path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' }, diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts new file mode 100644 index 0000000000..4325d8b6cb --- /dev/null +++ b/src/app/community-list-page/CommunityListDataSource.ts @@ -0,0 +1,74 @@ +import {CommunityForList, CommunityListService} from './CommunityListService'; +import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; +import {BehaviorSubject, Observable, of} from 'rxjs'; +import {catchError, finalize, map} from 'rxjs/operators'; + +export interface CommunityFlatNode { + expandable: boolean; + name: string; + level: number; + isExpanded?: boolean; + parent?: CommunityFlatNode; + community: CommunityForList; +} + +export class CommunityListDataSource implements DataSource { + + private communityListSubject = new BehaviorSubject([]); + private loadingSubject = new BehaviorSubject(false); + + public loading$ = this.loadingSubject.asObservable(); + + constructor(private communityListService: CommunityListService) {} + + connect(collectionViewer: CollectionViewer): Observable { + return this.communityListSubject.asObservable(); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.communityListSubject.complete(); + this.loadingSubject.complete(); + } + + loadCommunities() { + this.loadingSubject.next(true); + + this.communityListService.getCommunityList() + .pipe( + catchError(() => of([])), + finalize(() => this.loadingSubject.next(false)), + map((result: CommunityForList[]) => { + const communityFlatNodes: CommunityFlatNode[] = []; + const level = 0; + return this.transformListOfCommunities(result, level, communityFlatNodes, null); + }) + ) + .subscribe((communityFlatNode) => { + this.communityListSubject.next(communityFlatNode) + }); + }; + + transformListOfCommunities(listOfCommunities: CommunityForList[], + level: number, + communityFlatNodes: CommunityFlatNode[], + parent: CommunityFlatNode): CommunityFlatNode[] { + level++; + for (const community of listOfCommunities) { + const hasSubComs = ((!!community.subcoms && community.subcoms.length > 0)); + const communityFlatNode: CommunityFlatNode = { + expandable: hasSubComs, + name: community.name, + level: level, + isExpanded: false, + community: community, + parent: parent + } + communityFlatNodes.push(communityFlatNode); + if (hasSubComs) { + this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode); + } + } + return communityFlatNodes; + } + +} diff --git a/src/app/community-list-page/CommunityListService.ts b/src/app/community-list-page/CommunityListService.ts new file mode 100644 index 0000000000..940a79ec50 --- /dev/null +++ b/src/app/community-list-page/CommunityListService.ts @@ -0,0 +1,24 @@ +import {Injectable} from '@angular/core'; +import {Observable, of} from 'rxjs'; + +export interface CommunityForList { + name: string; + subcoms?: CommunityForList[]; + collections?: string[]; + parent?: string; +} + +@Injectable() +export class CommunityListService { + + private comList: CommunityForList[] = [ + {name: 'com1', subcoms: [{name: 'subcom1', subcoms: [{name: 'subsubcom1'}], collections: null, parent: 'com1'}], collections: ['col1', 'col2'], parent: null}, + {name: 'com2', subcoms: [{name: 'subcom2', subcoms: null, collections: null, parent: 'com2'}, {name: 'subcom3', subcoms: null, collections: null, parent: 'com2'}], collections: ['col3', 'col4'], parent: null}, + {name: 'com3', subcoms: [{name: 'subcom4', subcoms: null, collections: null, parent: 'com3'}], collections: ['col5'], parent: null}, + ]; + + public getCommunityList(): Observable { + return of(this.comList); + } + +} diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html new file mode 100644 index 0000000000..21e1b496a9 --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.html @@ -0,0 +1,2 @@ +

{{ 'communityList.title' | translate }}

+ diff --git a/src/app/community-list-page/community-list-page.component.spec.ts b/src/app/community-list-page/community-list-page.component.spec.ts new file mode 100644 index 0000000000..68a98276a2 --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommunityListPageComponent } from './community-list-page.component'; + +describe('CommunityListPageComponent', () => { + let component: CommunityListPageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommunityListPageComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityListPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts new file mode 100644 index 0000000000..ea1dda1bab --- /dev/null +++ b/src/app/community-list-page/community-list-page.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'ds-community-list-page', + templateUrl: './community-list-page.component.html', + styleUrls: ['./community-list-page.component.css'] +}) +export class CommunityListPageComponent implements OnInit { + + constructor() { } + + ngOnInit() { + } + +} diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts new file mode 100644 index 0000000000..31a7c4e51a --- /dev/null +++ b/src/app/community-list-page/community-list-page.module.ts @@ -0,0 +1,23 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { SharedModule } from '../shared/shared.module'; +import {CommunityListPageComponent} from './community-list-page.component'; +import {CommunityListPageRoutingModule} from './community-list-page.routing.module'; +import { CommunityListComponent } from './community-list/community-list.component'; +import {CdkTreeModule} from '@angular/cdk/tree'; + +@NgModule({ + imports: [ + CommonModule, + SharedModule, + CommunityListPageRoutingModule, + CdkTreeModule, + ], + declarations: [ + CommunityListPageComponent, + CommunityListComponent + ] +}) +export class CommunityListPageModule { + +} diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts new file mode 100644 index 0000000000..b3b254800b --- /dev/null +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import {CdkTreeModule} from '@angular/cdk/tree'; + +import {CommunityListPageComponent} from './community-list-page.component'; +import {CommunityListService} from './CommunityListService'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + { path: '', component: CommunityListPageComponent, pathMatch: 'full', data: { title: 'communityList.tabTitle' } } + ]), + CdkTreeModule, + ], + providers: [CommunityListService] +}) +export class CommunityListPageRoutingModule { } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html new file mode 100644 index 0000000000..1bbca95b74 --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -0,0 +1,25 @@ + + + + + +
{{node.name}}
+
+ + +
+ +
{{node.name}}
+
+
+
diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts new file mode 100644 index 0000000000..dadcc11251 --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CommunityListComponent } from './community-list.component'; + +describe('CommunityListComponent', () => { + let component: CommunityListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ CommunityListComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(CommunityListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts new file mode 100644 index 0000000000..009af5f0fb --- /dev/null +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -0,0 +1,37 @@ +import {Component, OnInit} from '@angular/core'; +import {CommunityListService} from '../CommunityListService'; +import {CommunityFlatNode, CommunityListDataSource} from '../CommunityListDataSource'; +import {FlatTreeControl} from '@angular/cdk/tree'; + +@Component({ + selector: 'ds-community-list', + templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.css'] +}) +export class CommunityListComponent implements OnInit { + + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.expandable + ); + + dataSource: CommunityListDataSource; + + constructor(private communityListService: CommunityListService) { } + + ngOnInit() { + this.dataSource = new CommunityListDataSource(this.communityListService); + this.dataSource.loadCommunities(); + } + + hasChild = (_: number, node: CommunityFlatNode) => node.expandable; + + shouldRender(node: CommunityFlatNode) { + const parent = node.parent; + return !parent || parent.isExpanded; + } + + numberReturn(length){ + return new Array(length); + } + +} diff --git a/yarn.lock b/yarn.lock index 69f4a072ae..d5c8d144de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,13 @@ dependencies: tslib "^1.9.0" +"@angular/cdk@^6.4.7": + version "6.4.7" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-6.4.7.tgz#1549b304dd412e82bd854cc55a7d5c6772ee0411" + integrity sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg== + dependencies: + tslib "^1.7.1" + "@angular/cli@^6.1.5": version "6.1.5" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.1.5.tgz#312c062631285ff06fd07ecde8afe22cdef5a0e1" @@ -10833,6 +10840,11 @@ tsickle@^0.32.1: source-map "^0.6.0" source-map-support "^0.5.0" +tslib@^1.7.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" From 69e8221867cf57ee85efa0c1d2c58ae8aa1cd0c1 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Thu, 10 Oct 2019 10:30:49 +0200 Subject: [PATCH 09/46] 65528: ComCol Tree with flatlist that gets generated at every (un)collapse > Only creates flatlist with expanded nodes --- .../CommunityListDataSource.ts | 22 +++++++++++++------ .../community-list.component.html | 2 +- .../community-list.component.ts | 16 +++++++++++--- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts index 4325d8b6cb..db1b817f7c 100644 --- a/src/app/community-list-page/CommunityListDataSource.ts +++ b/src/app/community-list-page/CommunityListDataSource.ts @@ -1,7 +1,7 @@ import {CommunityForList, CommunityListService} from './CommunityListService'; import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; import {BehaviorSubject, Observable, of} from 'rxjs'; -import {catchError, finalize, map} from 'rxjs/operators'; +import {catchError, finalize, map, take} from 'rxjs/operators'; export interface CommunityFlatNode { expandable: boolean; @@ -30,17 +30,18 @@ export class CommunityListDataSource implements DataSource { this.loadingSubject.complete(); } - loadCommunities() { + loadCommunities(expandedNodes: CommunityFlatNode[]) { this.loadingSubject.next(true); this.communityListService.getCommunityList() .pipe( + take(1), catchError(() => of([])), finalize(() => this.loadingSubject.next(false)), map((result: CommunityForList[]) => { const communityFlatNodes: CommunityFlatNode[] = []; const level = 0; - return this.transformListOfCommunities(result, level, communityFlatNodes, null); + return this.transformListOfCommunities(result, level, communityFlatNodes, null, expandedNodes); }) ) .subscribe((communityFlatNode) => { @@ -51,21 +52,28 @@ export class CommunityListDataSource implements DataSource { transformListOfCommunities(listOfCommunities: CommunityForList[], level: number, communityFlatNodes: CommunityFlatNode[], - parent: CommunityFlatNode): CommunityFlatNode[] { + parent: CommunityFlatNode, + expandedNodes: CommunityFlatNode[]): CommunityFlatNode[] { level++; for (const community of listOfCommunities) { const hasSubComs = ((!!community.subcoms && community.subcoms.length > 0)); + let expanded = false; + if (expandedNodes != null) { + const expandedNodesFound = expandedNodes.filter((node) => (node.name === community.name)); + expanded = (expandedNodesFound.length > 0); + } + console.log(community.name + 'is expanded: ' + expanded); const communityFlatNode: CommunityFlatNode = { expandable: hasSubComs, name: community.name, level: level, - isExpanded: false, + isExpanded: expanded, community: community, parent: parent } communityFlatNodes.push(communityFlatNode); - if (hasSubComs) { - this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode); + if (hasSubComs && communityFlatNode.isExpanded) { + this.transformListOfCommunities(community.subcoms, level, communityFlatNodes, communityFlatNode, expandedNodes); } } return communityFlatNodes; diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 1bbca95b74..eedf20b5e3 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -14,7 +14,7 @@
diff --git a/src/app/community-list-page/community-list/community-list.component.scss b/src/app/community-list-page/community-list/community-list.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index d6389b6a38..714024ee56 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -6,13 +6,14 @@ import {FlatTreeControl} from '@angular/cdk/tree'; @Component({ selector: 'ds-community-list', templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.scss'] }) export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.expandable + (node) => node.level, (node) => node.isExpandable ); dataSource: CommunityListDataSource; @@ -24,7 +25,7 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(null); } - hasChild = (_: number, node: FlatNode) => node.expandable; + hasChild = (_: number, node: FlatNode) => node.isExpandable; shouldRender(node: FlatNode) { const parent = node.parent; From 3daf35e4a4ad8923a8c761e70d028b3da0bdaf26 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:37:43 +0200 Subject: [PATCH 14/46] 65528: Renaming/Moving according to feedback; style and links added; chevrons fixed --- .../CommunityListDataSource.ts | 131 ----------------- .../CommunityListService.ts | 37 ----- .../community-list-adapter.ts | 134 ++++++++++++++++++ .../community-list-datasource.ts | 33 +++++ .../community-list-page.component.html | 6 +- .../community-list-page.routing.module.ts | 4 +- .../community-list.component.html | 59 +++++--- .../community-list.component.ts | 67 ++++----- 8 files changed, 243 insertions(+), 228 deletions(-) delete mode 100644 src/app/community-list-page/CommunityListDataSource.ts delete mode 100644 src/app/community-list-page/CommunityListService.ts create mode 100644 src/app/community-list-page/community-list-adapter.ts create mode 100644 src/app/community-list-page/community-list-datasource.ts diff --git a/src/app/community-list-page/CommunityListDataSource.ts b/src/app/community-list-page/CommunityListDataSource.ts deleted file mode 100644 index a3ef907e6c..0000000000 --- a/src/app/community-list-page/CommunityListDataSource.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; -import { PaginatedList } from '../core/data/paginated-list'; -import { RemoteData } from '../core/data/remote-data'; -import { hasValue, isNotEmpty } from '../shared/empty.util'; -import { CommunityListService } from './CommunityListService'; -import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; -import { BehaviorSubject, Observable, of as observableOf } from 'rxjs'; -import { - catchError, - filter, - finalize, - map, - switchMap, - take, -} from 'rxjs/operators'; -import { Community } from '../core/shared/community.model'; -import { Collection } from '../core/shared/collection.model'; - -export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection; -} - -const combineAndFlatten = (obsList: Array>) => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); - -const toFlatNode = ( - c: Community | Collection, - level: number, - isExpanded: boolean, - parent?: FlatNode -): FlatNode => ({ - isExpandable: c instanceof Community, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, -}); - -export class CommunityListDataSource implements DataSource { - - private communityList$ = new BehaviorSubject([]); - private loading$ = new BehaviorSubject(false); - - constructor(private communityListService: CommunityListService) { - } - - connect(collectionViewer: CollectionViewer): Observable { - return this.communityList$.asObservable(); - } - - disconnect(collectionViewer: CollectionViewer): void { - this.communityList$.complete(); - this.loading$.complete(); - } - - loadCommunities(expandedNodes: FlatNode[]): void { - this.loading$.next(true); - - this.communityListService.communities$ - .pipe( - take(1), - switchMap((result: Community[]) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - finalize(() => this.loading$.next(false)), - ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); - }; - - private transformListOfCommunities(listOfCommunities: Community[], - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfCommunities)) { - const obsList = listOfCommunities - .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); - - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const communityFlatNode = toFlatNode(community, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const subCommunityNodes$ = community.subcommunities.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) - ); - - obsList = [...obsList, subCommunityNodes$]; - - const collectionNodes$ = community.collections.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => - rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) - ) - ); - - obsList = [...obsList, collectionNodes$]; - } - - return combineAndFlatten(obsList); - } - -} diff --git a/src/app/community-list-page/CommunityListService.ts b/src/app/community-list-page/CommunityListService.ts deleted file mode 100644 index a00f2716f5..0000000000 --- a/src/app/community-list-page/CommunityListService.ts +++ /dev/null @@ -1,37 +0,0 @@ -import {Injectable} from '@angular/core'; -import {Observable, of} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import { map, take, tap } from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; - -@Injectable() -export class CommunityListService { - - communities$: Observable; - - config: PaginationComponentOptions; - sortConfig: SortOptions; - - constructor(private cds: CommunityDataService) { - this.config = new PaginationComponentOptions(); - this.config.id = 'top-level-pagination'; - this.config.pageSize = 10; - this.config.currentPage = 1; - this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList() - } - - private initTopCommunityList(): void { - this.communities$ = this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: { field: this.sortConfig.field, direction: this.sortConfig.direction } - }).pipe( - take(1), - map((results) => results.payload.page), - ); - } - -} diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts new file mode 100644 index 0000000000..84f427bd18 --- /dev/null +++ b/src/app/community-list-page/community-list-adapter.ts @@ -0,0 +1,134 @@ +import {Injectable} from '@angular/core'; +import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; +import {Observable, of as observableOf} from 'rxjs'; +import {CommunityDataService} from '../core/data/community-data.service'; +import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; +import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; +import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {Community} from '../core/shared/community.model'; +import {Collection} from '../core/shared/collection.model'; +import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {RemoteData} from '../core/data/remote-data'; +import {PaginatedList} from '../core/data/paginated-list'; + +export interface FlatNode { + isExpandable: boolean; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection; +} + +export const combineAndFlatten = (obsList: Array>): Observable => + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); + +export const toFlatNode = ( + c: Community | Collection, + level: number, + isExpanded: boolean, + parent?: FlatNode +): FlatNode => ({ + isExpandable: c instanceof Community, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, +}); + +@Injectable() +export class CommunityListAdapter { + + communities$: Observable; + + config: PaginationComponentOptions; + sortConfig: SortOptions; + + constructor(private cds: CommunityDataService) { + this.config = new PaginationComponentOptions(); + this.config.id = 'top-level-pagination'; + this.config.pageSize = 50; + this.config.currentPage = 1; + this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList() + } + + private initTopCommunityList(): void { + this.communities$ = this.cds.findTop({ + currentPage: this.config.currentPage, + elementsPerPage: this.config.pageSize, + sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + }).pipe( + take(1), + map((results) => results.payload.page), + ); + } + + loadCommunities(expandedNodes: FlatNode[]): Observable { + return this.communities$ + .pipe( + take(1), + switchMap((result: Community[]) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])) + ); + }; + + private transformListOfCommunities(listOfCommunities: Community[], + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfCommunities)) { + const obsList = listOfCommunities + .map((community: Community) => + this.transformCommunity(community, level, parent, expandedNodes)); + + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const communityFlatNode = toFlatNode(community, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const subCommunityNodes$ = community.subcommunities.pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) + ); + + obsList = [...obsList, subCommunityNodes$]; + + const collectionNodes$ = community.collections.pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => + rd.payload.page + .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) + ) + ); + + obsList = [...obsList, collectionNodes$]; + } + + return combineAndFlatten(obsList); + } + +} diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts new file mode 100644 index 0000000000..eab377f970 --- /dev/null +++ b/src/app/community-list-page/community-list-datasource.ts @@ -0,0 +1,33 @@ +import {CommunityListAdapter, FlatNode} from './community-list-adapter'; +import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; +import {BehaviorSubject, Observable,} from 'rxjs'; +import {finalize, take,} from 'rxjs/operators'; + +export class CommunityListDatasource implements DataSource { + + private communityList$ = new BehaviorSubject([]); + private loading$ = new BehaviorSubject(false); + + constructor(private communityListService: CommunityListAdapter) { + } + + connect(collectionViewer: CollectionViewer): Observable { + this.loadCommunities(null); + return this.communityList$.asObservable(); + } + + loadCommunities(expandedNodes: FlatNode[]) { + this.loading$.next(true); + + this.communityListService.loadCommunities(expandedNodes).pipe( + take(1), + finalize(() => this.loading$.next(false)), + ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); + } + + disconnect(collectionViewer: CollectionViewer): void { + this.communityList$.complete(); + this.loading$.complete(); + } + +} diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html index 21e1b496a9..bba67a7c6d 100644 --- a/src/app/community-list-page/community-list-page.component.html +++ b/src/app/community-list-page/community-list-page.component.html @@ -1,2 +1,4 @@ -

{{ 'communityList.title' | translate }}

- +
+

{{ 'communityList.title' | translate }}

+ +
diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index b3b254800b..90cd355bc5 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -3,7 +3,7 @@ import { RouterModule } from '@angular/router'; import {CdkTreeModule} from '@angular/cdk/tree'; import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListService} from './CommunityListService'; +import {CommunityListAdapter} from './community-list-adapter'; @NgModule({ imports: [ @@ -12,6 +12,6 @@ import {CommunityListService} from './CommunityListService'; ]), CdkTreeModule, ], - providers: [CommunityListService] + providers: [CommunityListAdapter] }) export class CommunityListPageRoutingModule { } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 55dc738ce6..a8fdc5bc7c 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,25 +1,38 @@ - - - - -
{{node.name}}
-
- - -
- -
{{node.name}}
-
-
+ + + + +
+ + {{node.name}} + +
+
+ {{node.payload.shortDescription}} +
+
+ + +
+ +
+ + {{node.name}} + +
+
+
+ {{node.payload.shortDescription}} +
+
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 714024ee56..015d71b7a9 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,46 +1,47 @@ import {Component, OnInit} from '@angular/core'; -import {CommunityListService} from '../CommunityListService'; -import {FlatNode, CommunityListDataSource} from '../CommunityListDataSource'; +import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; +import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; @Component({ - selector: 'ds-community-list', - templateUrl: './community-list.component.html', - styleUrls: ['./community-list.component.scss'] + selector: 'ds-community-list', + templateUrl: './community-list.component.html', + styleUrls: ['./community-list.component.scss'] }) export class CommunityListComponent implements OnInit { - private expandedNodes: FlatNode[] = []; + private expandedNodes: FlatNode[] = []; - treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable - ); + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.isExpandable + ); - dataSource: CommunityListDataSource; + dataSource: CommunityListDatasource; - constructor(private communityListService: CommunityListService) { } - - ngOnInit() { - this.dataSource = new CommunityListDataSource(this.communityListService); - this.dataSource.loadCommunities(null); - } - - hasChild = (_: number, node: FlatNode) => node.isExpandable; - - shouldRender(node: FlatNode) { - const parent = node.parent; - return !parent || parent.isExpanded; - } - - toggleExpanded(node: FlatNode) { - if (node.isExpanded) { - this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); - node.isExpanded = false; - } else { - this.expandedNodes.push(node); - node.isExpanded = true; + constructor(private communityListService: CommunityListAdapter) { + } + + ngOnInit() { + this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource.loadCommunities(null); + } + + hasChild = (_: number, node: FlatNode) => node.isExpandable; + + shouldRender(node: FlatNode) { + const parent = node.parent; + return !parent || parent.isExpanded; + } + + toggleExpanded(node: FlatNode) { + if (node.isExpanded) { + this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); + node.isExpanded = false; + } else { + this.expandedNodes.push(node); + node.isExpanded = true; + } + this.dataSource.loadCommunities(this.expandedNodes); } - this.dataSource.loadCommunities(this.expandedNodes); - } } From 3997ed2c99abf4da77557d90a5d7b8ce6577e2de Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:47:15 +0200 Subject: [PATCH 15/46] 65528: ComCol routing via routing modules instead of literal --- .../community-list/community-list.component.html | 4 ++-- .../community-list/community-list.component.ts | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index a8fdc5bc7c..373a36b0f8 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -6,7 +6,7 @@
- + {{node.name}}
@@ -26,7 +26,7 @@ aria-hidden="true">
- + {{node.name}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 015d71b7a9..187914e9e9 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,6 +2,8 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; +import {getCollectionPageRoute} from "../../+collection-page/collection-page-routing.module"; +import {getCommunityPageRoute} from "../../+community-page/community-page-routing.module"; @Component({ selector: 'ds-community-list', @@ -44,4 +46,12 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(this.expandedNodes); } + getCollectionRoute(node: FlatNode): string { + return getCollectionPageRoute(node.id); + } + + getCommunityRoute(node: FlatNode): string { + return getCommunityPageRoute(node.id); + } + } From f4686ea6cfba3fedd58fc0fcaea4f28ca0f8766d Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Fri, 11 Oct 2019 17:47:41 +0200 Subject: [PATCH 16/46] 65528: styleLint --- .../community-list/community-list.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 187914e9e9..0e571bd408 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,8 +2,8 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; -import {getCollectionPageRoute} from "../../+collection-page/collection-page-routing.module"; -import {getCommunityPageRoute} from "../../+community-page/community-page-routing.module"; +import {getCollectionPageRoute} from '../../+collection-page/collection-page-routing.module'; +import {getCommunityPageRoute} from '../../+community-page/community-page-routing.module'; @Component({ selector: 'ds-community-list', From c99b6adb3111c5d3809f2d33a888a268405ad746 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 12:42:37 +0200 Subject: [PATCH 17/46] 65600: ComCol-Tree load more links message, not yet functional --- resources/i18n/en.json5 | 1 + .../community-list-adapter.ts | 90 +++++++++++++++---- .../community-list.component.html | 19 +++- .../community-list.component.ts | 13 ++- 4 files changed, 102 insertions(+), 21 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 144b3128c7..7ed08ac732 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -343,6 +343,7 @@ "communityList.tabTitle": "DSpace - Community List", "communityList.title": "List of Communities", + "communityList.showMore": "Show More", diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index 84f427bd18..c9443675a7 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,10 +1,10 @@ import {Injectable} from '@angular/core'; import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of as observableOf} from 'rxjs'; +import {Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {catchError, filter, map, switchMap, take, tap} from 'rxjs/operators'; import {Community} from '../core/shared/community.model'; import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; @@ -19,6 +19,7 @@ export interface FlatNode { isExpanded?: boolean; parent?: FlatNode; payload: Community | Collection; + isShowMoreNode: boolean; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -40,12 +41,28 @@ export const toFlatNode = ( isExpanded, parent, payload: c, + isShowMoreNode: false, +}); + +export const showMoreFlatNode = ( + c: Community | Collection, + level: number, + parent?: FlatNode +): FlatNode => ({ + isExpandable: false, + name: c.name, + id: c.id, + level: level, + isExpanded: false, + parent: parent, + payload: c, + isShowMoreNode: true, }); @Injectable() export class CommunityListAdapter { - communities$: Observable; + payload$: Observable>; config: PaginationComponentOptions; sortConfig: SortOptions; @@ -53,42 +70,48 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 50; + this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() } private initTopCommunityList(): void { - this.communities$ = this.cds.findTop({ + this.payload$ = this.cds.findTop({ currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} }).pipe( take(1), - map((results) => results.payload.page), + map((results) => results.payload), ); } loadCommunities(expandedNodes: FlatNode[]): Observable { - return this.communities$ + return this.payload$ .pipe( take(1), - switchMap((result: Community[]) => { + switchMap((result: PaginatedList) => { return this.transformListOfCommunities(result, 0, null, expandedNodes); }), - catchError(() => observableOf([])) + catchError(() => observableOf([])), + tap((results) => console.log('endload', results)), ); }; - private transformListOfCommunities(listOfCommunities: Community[], + private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfCommunities)) { - const obsList = listOfCommunities + if (isNotEmpty(listOfPaginatedCommunities.page)) { + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > listOfPaginatedCommunities.elementsPerPage); + let obsList = listOfPaginatedCommunities.page .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); + this.transformCommunity(community, level, parent, expandedNodes, isNotAllCommunities)); + + if (isNotAllCommunities) { + obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; + } return combineAndFlatten(obsList); } else { @@ -96,7 +119,7 @@ export class CommunityListAdapter { } } - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[], isNotAllCommunities: boolean): Observable { let isExpanded = false; if (isNotEmpty(expandedNodes)) { isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); @@ -111,7 +134,7 @@ export class CommunityListAdapter { filter((rd: RemoteData>) => rd.hasSucceeded), take(1), switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload.page, level + 1, communityFlatNode, expandedNodes)) + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) ); obsList = [...obsList, subCommunityNodes$]; @@ -119,16 +142,45 @@ export class CommunityListAdapter { const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), take(1), - map((rd: RemoteData>) => - rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)) + tap((results) => console.log('collectionstap', results)), + map((rd: RemoteData>) => { + let nodes$ = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); + if (rd.payload.elementsPerPage < rd.payload.totalElements) { + nodes$ = [...nodes$, this.addPossibleShowMoreCollectionFlatNode(level + 1, parent)]; + } + return nodes$; + } ) ); - obsList = [...obsList, collectionNodes$]; } return combineAndFlatten(obsList); } + private addPossibleShowMoreComunityFlatNode(level: number, parent: FlatNode): Observable { + const dummyCommunity = Object.assign(new Community(), { + id: '999999', + metadata: { + 'dc.title': [ + { language: 'en_US', value: 'Test' } + ] + } + }) + return of([showMoreFlatNode(dummyCommunity, level, parent)]); + } + + private addPossibleShowMoreCollectionFlatNode(level: number, parent: FlatNode): FlatNode { + const dummyCollection = Object.assign(new Collection(), { + id: '999999', + metadata: { + 'dc.title': [ + { language: 'en_US', value: 'Test' } + ] + } + }) + return showMoreFlatNode(dummyCollection, level, parent); + } + } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 373a36b0f8..1aaf07bbcb 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,6 +1,23 @@ + + + +
+ +
+ {{ 'communityList.showMore' | translate }} +
+
+
- diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 0e571bd408..6d0ad09ab0 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -28,7 +28,13 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(null); } - hasChild = (_: number, node: FlatNode) => node.isExpandable; + hasChild(_: number, node: FlatNode) { + return node.isExpandable; + } + + isShowMore(_: number, node: FlatNode) { + return node.isShowMoreNode; + } shouldRender(node: FlatNode) { const parent = node.parent; @@ -54,4 +60,9 @@ export class CommunityListComponent implements OnInit { return getCommunityPageRoute(node.id); } + getNextPage(): void { + console.log('go to next page'); + // TODO + } + } From e950c23f40a82f9d8a01a0740fb24576faa8e337 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 13:41:05 +0200 Subject: [PATCH 18/46] 65600: Theme changes&improvements and route calculation moved to tree creation --- .../community-list-adapter.ts | 6 ++- .../community-list.component.html | 40 ++++++++++++------- .../community-list.component.ts | 16 +------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index c9443675a7..b5e5ef4ca5 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -10,6 +10,8 @@ import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; +import {getCommunityPageRoute} from "../+community-page/community-page-routing.module"; +import {getCollectionPageRoute} from "../+collection-page/collection-page-routing.module"; export interface FlatNode { isExpandable: boolean; @@ -20,6 +22,7 @@ export interface FlatNode { parent?: FlatNode; payload: Community | Collection; isShowMoreNode: boolean; + route?: string; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -42,6 +45,7 @@ export const toFlatNode = ( parent, payload: c, isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); export const showMoreFlatNode = ( @@ -70,7 +74,7 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 5; + this.config.pageSize = 10; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 1aaf07bbcb..abb3e61c59 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -18,17 +18,23 @@ - - -
- - {{node.name}} - -
-
- {{node.payload.shortDescription}} + class="example-tree-node"> +
+ +
+ + {{node.name}} + +
+
+
+
+ + {{node.payload.shortDescription}} +
@@ -38,18 +44,22 @@
- + {{node.name}}
-
- {{node.payload.shortDescription}} +
+
+ + {{node.payload.shortDescription}} +
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 6d0ad09ab0..0a9d369cdf 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,8 +2,6 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; -import {getCollectionPageRoute} from '../../+collection-page/collection-page-routing.module'; -import {getCommunityPageRoute} from '../../+community-page/community-page-routing.module'; @Component({ selector: 'ds-community-list', @@ -13,6 +11,7 @@ import {getCommunityPageRoute} from '../../+community-page/community-page-routin export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; + private Arr = Array; treeControl = new FlatTreeControl( (node) => node.level, (node) => node.isExpandable @@ -36,11 +35,6 @@ export class CommunityListComponent implements OnInit { return node.isShowMoreNode; } - shouldRender(node: FlatNode) { - const parent = node.parent; - return !parent || parent.isExpanded; - } - toggleExpanded(node: FlatNode) { if (node.isExpanded) { this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); @@ -52,14 +46,6 @@ export class CommunityListComponent implements OnInit { this.dataSource.loadCommunities(this.expandedNodes); } - getCollectionRoute(node: FlatNode): string { - return getCollectionPageRoute(node.id); - } - - getCommunityRoute(node: FlatNode): string { - return getCommunityPageRoute(node.id); - } - getNextPage(): void { console.log('go to next page'); // TODO From 1400af3a5ee7cba8e242b081669f00ea308c5796 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 14:00:21 +0200 Subject: [PATCH 19/46] 65600: Indentation description more than one line --- .../community-list/community-list.component.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index abb3e61c59..5613c09415 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -30,9 +30,10 @@
-
-
- +
+
+ {{node.payload.shortDescription}}
@@ -54,8 +55,8 @@
-
-
+
+
{{node.payload.shortDescription}} From 48d893e9757f580428ade83e368376f90ef688a4 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 15 Oct 2019 15:01:51 +0200 Subject: [PATCH 20/46] 65600: Pagination on top level communities by combineFlatten list of consecutive page payloads --- .../community-list-adapter.ts | 42 ++++++++++++------- .../community-list.component.ts | 8 ++-- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index b5e5ef4ca5..d7ed317f8b 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of, of as observableOf} from 'rxjs'; +import {merge, Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; @@ -10,8 +10,8 @@ import {Collection} from '../core/shared/collection.model'; import {hasValue, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from "../+community-page/community-page-routing.module"; -import {getCollectionPageRoute} from "../+collection-page/collection-page-routing.module"; +import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; +import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; export interface FlatNode { isExpandable: boolean; @@ -66,7 +66,7 @@ export const showMoreFlatNode = ( @Injectable() export class CommunityListAdapter { - payload$: Observable>; + payload$: Array>>; config: PaginationComponentOptions; sortConfig: SortOptions; @@ -74,33 +74,46 @@ export class CommunityListAdapter { constructor(private cds: CommunityDataService) { this.config = new PaginationComponentOptions(); this.config.id = 'top-level-pagination'; - this.config.pageSize = 10; + this.config.pageSize = 5; this.config.currentPage = 1; this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() } private initTopCommunityList(): void { - this.payload$ = this.cds.findTop({ + this.payload$ = [this.cds.findTop({ currentPage: this.config.currentPage, elementsPerPage: this.config.pageSize, sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} }).pipe( take(1), map((results) => results.payload), - ); + )]; + } + + getNextPageTopCommunities(): void { + this.config.currentPage = this.config.currentPage + 1; + this.payload$ = [...this.payload$, this.cds.findTop({ + currentPage: this.config.currentPage, + elementsPerPage: this.config.pageSize, + sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + }).pipe( + take(1), + map((results) => results.payload), + )]; } loadCommunities(expandedNodes: FlatNode[]): Observable { - return this.payload$ - .pipe( + const res = this.payload$.map((payload) => { + return payload.pipe( take(1), switchMap((result: PaginatedList) => { return this.transformListOfCommunities(result, 0, null, expandedNodes); }), catchError(() => observableOf([])), - tap((results) => console.log('endload', results)), ); + }); + return combineAndFlatten(res); }; private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, @@ -108,12 +121,12 @@ export class CommunityListAdapter { parent: FlatNode, expandedNodes: FlatNode[]): Observable { if (isNotEmpty(listOfPaginatedCommunities.page)) { - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > listOfPaginatedCommunities.elementsPerPage); + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * this.config.currentPage)); let obsList = listOfPaginatedCommunities.page .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes, isNotAllCommunities)); + this.transformCommunity(community, level, parent, expandedNodes)); - if (isNotAllCommunities) { + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > this.config.currentPage) { obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; } @@ -123,7 +136,7 @@ export class CommunityListAdapter { } } - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[], isNotAllCommunities: boolean): Observable { + private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { let isExpanded = false; if (isNotEmpty(expandedNodes)) { isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); @@ -146,7 +159,6 @@ export class CommunityListAdapter { const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), take(1), - tap((results) => console.log('collectionstap', results)), map((rd: RemoteData>) => { let nodes$ = rd.payload.page .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 0a9d369cdf..7c551342e8 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -19,11 +19,11 @@ export class CommunityListComponent implements OnInit { dataSource: CommunityListDatasource; - constructor(private communityListService: CommunityListAdapter) { + constructor(private communityListAdapter: CommunityListAdapter) { } ngOnInit() { - this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource = new CommunityListDatasource(this.communityListAdapter); this.dataSource.loadCommunities(null); } @@ -47,8 +47,8 @@ export class CommunityListComponent implements OnInit { } getNextPage(): void { - console.log('go to next page'); - // TODO + this.communityListAdapter.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); } } From a4bf1a64c78579d5869b2bb9db8b9d846a809912 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 12:37:23 +0200 Subject: [PATCH 21/46] 65600: Pagination for subcommunities works; not yet for collections since it needs authorisation --- .../community-list-adapter.ts | 89 ++++++++++++------- .../community-list.component.html | 8 +- .../community-list.component.ts | 29 +++++- src/app/core/data/community-data.service.ts | 16 ++++ 4 files changed, 102 insertions(+), 40 deletions(-) diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index d7ed317f8b..541a5d0189 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -4,14 +4,15 @@ import {merge, Observable, of, of as observableOf} from 'rxjs'; import {CommunityDataService} from '../core/data/community-data.service'; import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take, tap} from 'rxjs/operators'; +import {catchError, defaultIfEmpty, filter, map, switchMap, take, tap} from 'rxjs/operators'; import {Community} from '../core/shared/community.model'; import {Collection} from '../core/shared/collection.model'; -import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {hasValue, isEmpty, isNotEmpty} from '../shared/empty.util'; import {RemoteData} from '../core/data/remote-data'; import {PaginatedList} from '../core/data/paginated-list'; import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; +import {CollectionDataService} from '../core/data/collection-data.service'; export interface FlatNode { isExpandable: boolean; @@ -23,6 +24,8 @@ export interface FlatNode { payload: Community | Collection; isShowMoreNode: boolean; route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } export const combineAndFlatten = (obsList: Array>): Observable => @@ -66,37 +69,42 @@ export const showMoreFlatNode = ( @Injectable() export class CommunityListAdapter { - payload$: Array>>; + payloads$: Array>>; - config: PaginationComponentOptions; - sortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - constructor(private cds: CommunityDataService) { - this.config = new PaginationComponentOptions(); - this.config.id = 'top-level-pagination'; - this.config.pageSize = 5; - this.config.currentPage = 1; - this.sortConfig = new SortOptions('dc.title', SortDirection.ASC); + maxSubCommunitiesPerPage: number; + + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); this.initTopCommunityList() + + this.maxSubCommunitiesPerPage = 3; } private initTopCommunityList(): void { - this.payload$ = [this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} }).pipe( take(1), map((results) => results.payload), )]; + } getNextPageTopCommunities(): void { - this.config.currentPage = this.config.currentPage + 1; - this.payload$ = [...this.payload$, this.cds.findTop({ - currentPage: this.config.currentPage, - elementsPerPage: this.config.pageSize, - sort: {field: this.sortConfig.field, direction: this.sortConfig.direction} + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} }).pipe( take(1), map((results) => results.payload), @@ -104,7 +112,7 @@ export class CommunityListAdapter { } loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payload$.map((payload) => { + const res = this.payloads$.map((payload) => { return payload.pipe( take(1), switchMap((result: PaginatedList) => { @@ -121,12 +129,16 @@ export class CommunityListAdapter { parent: FlatNode, expandedNodes: FlatNode[]): Observable { if (isNotEmpty(listOfPaginatedCommunities.page)) { - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * this.config.currentPage)); + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); let obsList = listOfPaginatedCommunities.page - .map((community: Community) => - this.transformCommunity(community, level, parent, expandedNodes)); - - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > this.config.currentPage) { + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; } @@ -147,14 +159,25 @@ export class CommunityListAdapter { let obsList = [observableOf([communityFlatNode])]; if (isExpanded) { - const subCommunityNodes$ = community.subcommunities.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); + const currentPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms$ = []; + for (let i = 1; i <= currentPage ; i++) { + const p = this.communityDataService.findSubCommunitiesPerParentCommunity(community.uuid,{elementsPerPage: this.maxSubCommunitiesPerPage, currentPage: i}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - obsList = [...obsList, subCommunityNodes$]; + ); + subcoms$ = [...subcoms$, p]; + } + + obsList = [...obsList, combineAndFlatten(subcoms$)]; + + // need to be authorized (logged in) to receive collections this way + // const cols = this.collectionDataService.getAuthorizedCollectionByCommunity(community.uuid,{elementsPerPage: 2}); + // cols.pipe(take(1)).subscribe((val) => console.log('cols:', val)); const collectionNodes$ = community.collections.pipe( filter((rd: RemoteData>) => rd.hasSucceeded), diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 5613c09415..77e9e1ee40 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -4,10 +4,10 @@
+ (click)="getNextPage(node)"> @@ -26,7 +26,7 @@
- {{node.name}} + {{node.name}} {{node.id}}
@@ -51,7 +51,7 @@
- {{node.name}} + {{node.name}} {{node.id}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 7c551342e8..c45e5098ab 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -2,6 +2,9 @@ import {Component, OnInit} from '@angular/core'; import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; +import {Collection} from '../../core/shared/collection.model'; +import {Community} from '../../core/shared/community.model'; +import {isEmpty} from "../../shared/empty.util"; @Component({ selector: 'ds-community-list', @@ -42,13 +45,33 @@ export class CommunityListComponent implements OnInit { } else { this.expandedNodes.push(node); node.isExpanded = true; + if (isEmpty(node.currentCollectionPage)) { + node.currentCollectionPage = 1; + } + if (isEmpty(node.currentCommunityPage)) { + node.currentCommunityPage = 1; + } } this.dataSource.loadCommunities(this.expandedNodes); } - getNextPage(): void { - this.communityListAdapter.getNextPageTopCommunities(); - this.dataSource.loadCommunities(this.expandedNodes); + getNextPage(node: FlatNode): void { + if (node.parent != null) { + if (node.parent.isExpandable) { + if (node.payload instanceof Collection) { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; + } + if (node.payload instanceof Community) { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; + } + } + this.dataSource.loadCommunities(this.expandedNodes); + } else { + this.communityListAdapter.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); + } } } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index cc55fe6869..f29c3c8833 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -23,6 +23,7 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; export class CommunityDataService extends ComColDataService { protected linkPath = 'communities'; protected topLinkPath = 'communities/search/top'; + protected subcommunitiesLinkPath = 'communities/search/subCommunities'; protected cds = this; constructor( @@ -55,4 +56,19 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } + + findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindAllOptions = {}): Observable>> { + const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); + console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); + + hrefObs.pipe( + filter((href: string) => hasValue(href)), + take(1)) + .subscribe((href: string) => { + const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + this.requestService.configure(request); + }); + + return this.rdbService.buildList(hrefObs) as Observable>>; + } } From 98c8eb558a4ad46b04d127549ae08fb2473035f7 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 12:44:56 +0200 Subject: [PATCH 22/46] 65600: Renamed FindAllOptions to FindListOptions --- .../bitstream-formats.component.ts | 6 ++--- .../core/cache/models/search-param.model.ts | 2 +- src/app/core/config/config.service.spec.ts | 4 ++-- src/app/core/config/config.service.ts | 6 ++--- .../data/bitstream-format-data.service.ts | 6 ++--- src/app/core/data/collection-data.service.ts | 12 +++++----- src/app/core/data/comcol-data.service.spec.ts | 4 ++-- src/app/core/data/comcol-data.service.ts | 4 ++-- src/app/core/data/community-data.service.ts | 6 ++--- src/app/core/data/data.service.spec.ts | 6 ++--- src/app/core/data/data.service.ts | 22 +++++++++---------- .../core/data/dspace-object-data.service.ts | 4 ++-- src/app/core/data/item-data.service.spec.ts | 5 ++--- src/app/core/data/item-data.service.ts | 6 ++--- .../core/data/metadata-schema-data.service.ts | 4 ++-- src/app/core/data/request.models.ts | 4 ++-- src/app/core/data/resource-policy.service.ts | 4 ++-- src/app/core/eperson/eperson.service.ts | 4 ++-- src/app/core/eperson/group-eperson.service.ts | 4 ++-- .../submission/workflowitem-data.service.ts | 4 ++-- .../submission/workspaceitem-data.service.ts | 4 ++-- src/app/core/tasks/tasks.service.ts | 4 ++-- .../submission-form-collection.component.ts | 4 ++-- 23 files changed, 64 insertions(+), 65 deletions(-) diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index cb7aa1ef91..ec4003c108 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; import { map, switchMap, take } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit { * The current pagination configuration for the page used by the FindAll method * Currently simply renders all bitstream formats */ - config: FindAllOptions = Object.assign(new FindAllOptions(), { + config: FindListOptions = Object.assign(new FindListOptions(), { elementsPerPage: 20 }); @@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit { * @param event The page change event */ onPageChange(event) { - this.config = Object.assign(new FindAllOptions(), this.config, { + this.config = Object.assign(new FindListOptions(), this.config, { currentPage: event, }); this.pageConfig.currentPage = event; diff --git a/src/app/core/cache/models/search-param.model.ts b/src/app/core/cache/models/search-param.model.ts index a33bbee5e6..3881dbe8b7 100644 --- a/src/app/core/cache/models/search-param.model.ts +++ b/src/app/core/cache/models/search-param.model.ts @@ -1,6 +1,6 @@ /** - * Class representing a query parameter (query?fieldName=fieldValue) used in FindAllOptions object + * Class representing a query parameter (query?fieldName=fieldValue) used in FindListOptions object */ export class SearchParam { constructor(public fieldName: string, public fieldValue: any) { diff --git a/src/app/core/config/config.service.spec.ts b/src/app/core/config/config.service.spec.ts index 87add6b656..402ee88b81 100644 --- a/src/app/core/config/config.service.spec.ts +++ b/src/app/core/config/config.service.spec.ts @@ -3,7 +3,7 @@ import { TestScheduler } from 'rxjs/testing'; import { getMockRequestService } from '../../shared/mocks/mock-request.service'; import { ConfigService } from './config.service'; import { RequestService } from '../data/request.service'; -import { ConfigRequest, FindAllOptions } from '../data/request.models'; +import { ConfigRequest, FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub'; @@ -27,7 +27,7 @@ describe('ConfigService', () => { let requestService: RequestService; let halService: any; - const findOptions: FindAllOptions = new FindAllOptions(); + const findOptions: FindListOptions = new FindListOptions(); const scopeName = 'traditional'; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; diff --git a/src/app/core/config/config.service.ts b/src/app/core/config/config.service.ts index 340a7a97d6..db14c4a256 100644 --- a/src/app/core/config/config.service.ts +++ b/src/app/core/config/config.service.ts @@ -2,7 +2,7 @@ import { merge as observableMerge, Observable, throwError as observableThrowErro import { distinctUntilChanged, filter, map, mergeMap, tap } from 'rxjs/operators'; import { RequestService } from '../data/request.service'; import { ConfigSuccessResponse } from '../cache/response.models'; -import { ConfigRequest, FindAllOptions, RestRequest } from '../data/request.models'; +import { ConfigRequest, FindListOptions, RestRequest } from '../data/request.models'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ConfigData } from './config-data'; @@ -35,7 +35,7 @@ export abstract class ConfigService { return `${endpoint}/${resourceName}`; } - protected getConfigSearchHref(endpoint, options: FindAllOptions = {}): string { + protected getConfigSearchHref(endpoint, options: FindListOptions = {}): string { let result; const args = []; @@ -93,7 +93,7 @@ export abstract class ConfigService { distinctUntilChanged()); } - public getConfigBySearch(options: FindAllOptions = {}): Observable { + public getConfigBySearch(options: FindListOptions = {}): Observable { return this.halService.getEndpoint(this.linkPath).pipe( map((endpoint: string) => this.getConfigSearchHref(endpoint, options)), filter((href: string) => isNotEmpty(href)), diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts index bdf9b16acf..7255ed3663 100644 --- a/src/app/core/data/bitstream-format-data.service.ts +++ b/src/app/core/data/bitstream-format-data.service.ts @@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models'; +import { DeleteByIDRequest, FindListOptions, PostRequest, PutRequest } from './request.models'; import { Observable } from 'rxjs'; import { find, map, tap } from 'rxjs/operators'; import { configureRequest, getResponseFromEntry } from '../shared/operators'; @@ -54,10 +54,10 @@ export class BitstreamFormatDataService extends DataService { /** * Get the endpoint for browsing bitstream formats - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ - getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 7ec31d8970..88cc80a9a8 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { Observable } from 'rxjs/internal/Observable'; -import { FindAllOptions, GetRequest } from './request.models'; +import { FindListOptions, GetRequest } from './request.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { configureRequest } from '../shared/operators'; @@ -50,11 +50,11 @@ export class CollectionDataService extends ComColDataService { /** * Get all collections the user is authorized to submit to * - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollection(options: FindAllOptions = {}): Observable>> { + getAuthorizedCollection(options: FindListOptions = {}): Observable>> { const searchHref = 'findAuthorized'; return this.searchBy(searchHref, options).pipe( @@ -65,11 +65,11 @@ export class CollectionDataService extends ComColDataService { * Get all collections the user is authorized to submit to, by community * * @param communityId The community id - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return Observable>> * collection list */ - getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable>> { + getAuthorizedCollectionByCommunity(communityId: string, options: FindListOptions = {}): Observable>> { const searchHref = 'findAuthorizedByCommunity'; options.searchParams = [new SearchParam('uuid', communityId)]; @@ -85,7 +85,7 @@ export class CollectionDataService extends ComColDataService { */ hasAuthorizedCollection(): Observable { const searchHref = 'findAuthorized'; - const options = new FindAllOptions(); + const options = new FindListOptions(); options.elementsPerPage = 1; return this.searchBy(searchHref, options).pipe( diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index 5cc474dff9..ffefbba6f6 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -8,7 +8,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CoreState } from '../core.reducers'; import { ComColDataService } from './comcol-data.service'; import { CommunityDataService } from './community-data.service'; -import { FindAllOptions, FindByIDRequest } from './request.models'; +import { FindListOptions, FindByIDRequest } from './request.models'; import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; @@ -66,7 +66,7 @@ describe('ComColDataService', () => { const dataBuildService = {} as NormalizedObjectBuildService; const scopeID = 'd9d30c0c-69b7-4369-8397-ca67c888974d'; - const options = Object.assign(new FindAllOptions(), { + const options = Object.assign(new FindListOptions(), { scopeID: scopeID }); const getRequestEntry$ = (successful: boolean) => { diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 68eb3e4880..62e2acfa87 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -6,7 +6,7 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; -import { FindAllOptions, FindByIDRequest } from './request.models'; +import { FindListOptions, FindByIDRequest } from './request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { getResponseFromEntry } from '../shared/operators'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -26,7 +26,7 @@ export abstract class ComColDataService extends DataS * @return { Observable } * an Observable containing the scoped URL */ - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { if (isEmpty(options.scopeID)) { return this.halService.getEndpoint(linkPath); } else { diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index f29c3c8833..1e3875edd4 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -9,7 +9,7 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions, FindAllRequest } from './request.models'; +import { FindListOptions, FindAllRequest } from './request.models'; import { RemoteData } from './remote-data'; import { hasValue } from '../../shared/empty.util'; import { Observable } from 'rxjs'; @@ -44,7 +44,7 @@ export class CommunityDataService extends ComColDataService { return this.halService.getEndpoint(this.linkPath); } - findTop(options: FindAllOptions = {}): Observable>> { + findTop(options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.topLinkPath); hrefObs.pipe( filter((href: string) => hasValue(href)), @@ -57,7 +57,7 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } - findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindAllOptions = {}): Observable>> { + findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); diff --git a/src/app/core/data/data.service.spec.ts b/src/app/core/data/data.service.spec.ts index ab3cf0ac16..f2f10eff35 100644 --- a/src/app/core/data/data.service.spec.ts +++ b/src/app/core/data/data.service.spec.ts @@ -6,7 +6,7 @@ import { CoreState } from '../core.reducers'; import { Store } from '@ngrx/store'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Observable, of as observableOf } from 'rxjs'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { SortDirection, SortOptions } from '../cache/models/sort-options.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { compare, Operation } from 'fast-json-patch'; @@ -40,7 +40,7 @@ class TestService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return observableOf(endpoint); } } @@ -53,7 +53,7 @@ class DummyChangeAnalyzer implements ChangeAnalyzer { } describe('DataService', () => { let service: TestService; - let options: FindAllOptions; + let options: FindListOptions; const requestService = {} as RequestService; const halService = {} as HALEndpointService; const rdbService = {} as RemoteDataBuildService; diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index bbfcde2677..69b2cdfcb5 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -14,7 +14,7 @@ import { RemoteData } from './remote-data'; import { CreateRequest, DeleteByIDRequest, - FindAllOptions, + FindListOptions, FindAllRequest, FindByIDRequest, GetRequest @@ -54,17 +54,17 @@ export abstract class DataService { */ protected responseMsToLive: number; - public abstract getBrowseEndpoint(options: FindAllOptions, linkPath?: string): Observable + public abstract getBrowseEndpoint(options: FindListOptions, linkPath?: string): Observable /** * Create the HREF with given options object * - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @param linkPath The link path for the object * @return {Observable} * Return an observable that emits created HREF */ - protected getFindAllHref(options: FindAllOptions = {}, linkPath?: string): Observable { + protected getFindAllHref(options: FindListOptions = {}, linkPath?: string): Observable { let result: Observable; const args = []; @@ -77,11 +77,11 @@ export abstract class DataService { * Create the HREF for a specific object's search method with given options object * * @param searchMethod The search method for the object - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable} * Return an observable that emits created HREF */ - protected getSearchByHref(searchMethod: string, options: FindAllOptions = {}): Observable { + protected getSearchByHref(searchMethod: string, options: FindListOptions = {}): Observable { let result: Observable; const args = []; @@ -101,11 +101,11 @@ export abstract class DataService { * * @param href$ The HREF to which the query string should be appended * @param args Array with additional params to combine with query string - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable} * Return an observable that emits created HREF */ - protected buildHrefFromFindOptions(href$: Observable, args: string[], options: FindAllOptions): Observable { + protected buildHrefFromFindOptions(href$: Observable, args: string[], options: FindListOptions): Observable { if (hasValue(options.currentPage) && typeof options.currentPage === 'number') { /* TODO: this is a temporary fix for the pagination start index (0 or 1) discrepancy between the rest and the frontend respectively */ @@ -127,7 +127,7 @@ export abstract class DataService { } } - findAll(options: FindAllOptions = {}): Observable>> { + findAll(options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options); hrefObs.pipe( @@ -194,11 +194,11 @@ export abstract class DataService { * Make a new FindAllRequest with given search method * * @param searchMethod The search method for the object - * @param options The [[FindAllOptions]] object + * @param options The [[FindListOptions]] object * @return {Observable>} * Return an observable that emits response from the server */ - protected searchBy(searchMethod: string, options: FindAllOptions = {}): Observable>> { + protected searchBy(searchMethod: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getSearchByHref(searchMethod, options); diff --git a/src/app/core/data/dspace-object-data.service.ts b/src/app/core/data/dspace-object-data.service.ts index bb02afbcd1..002ac3cdbc 100644 --- a/src/app/core/data/dspace-object-data.service.ts +++ b/src/app/core/data/dspace-object-data.service.ts @@ -8,7 +8,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DataService } from './data.service'; import { RemoteData } from './remote-data'; import { RequestService } from './request.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; @@ -32,7 +32,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts index 36b8e6b3c5..44c5f48cfe 100644 --- a/src/app/core/data/item-data.service.spec.ts +++ b/src/app/core/data/item-data.service.spec.ts @@ -2,14 +2,13 @@ import { Store } from '@ngrx/store'; import { cold, getTestScheduler } from 'jasmine-marbles'; import { TestScheduler } from 'rxjs/testing'; import { BrowseService } from '../browse/browse.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { CoreState } from '../core.reducers'; import { ItemDataService } from './item-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DeleteRequest, - FindAllOptions, + FindListOptions, GetRequest, MappedCollectionsRequest, PostRequest, @@ -58,7 +57,7 @@ describe('ItemDataService', () => { } as HALEndpointService; const scopeID = '4af28e99-6a9c-4036-a199-e1b587046d39'; - const options = Object.assign(new FindAllOptions(), { + const options = Object.assign(new FindListOptions(), { scopeID: scopeID, sort: { field: '', diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index e616cb8020..affcae619c 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -14,7 +14,7 @@ import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { DeleteRequest, - FindAllOptions, + FindListOptions, MappedCollectionsRequest, PatchRequest, PostRequest, PutRequest, @@ -59,10 +59,10 @@ export class ItemDataService extends DataService { /** * Get the endpoint for browsing items * (When options.sort.field is empty, the default field to browse by will be 'dc.date.issued') - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ - public getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { let field = 'dc.date.issued'; if (options.sort && options.sort.field) { field = options.sort.field; diff --git a/src/app/core/data/metadata-schema-data.service.ts b/src/app/core/data/metadata-schema-data.service.ts index b15dd6865f..662eaa6c7c 100644 --- a/src/app/core/data/metadata-schema-data.service.ts +++ b/src/app/core/data/metadata-schema-data.service.ts @@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers'; import { DataService } from './data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { ObjectCacheService } from '../cache/object-cache.service'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { HttpClient } from '@angular/common/http'; @@ -33,7 +33,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } } diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index a7d11089df..6396d7a374 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -138,7 +138,7 @@ export class FindByIDRequest extends GetRequest { } } -export class FindAllOptions { +export class FindListOptions { scopeID?: string; elementsPerPage?: number; currentPage?: number; @@ -151,7 +151,7 @@ export class FindAllRequest extends GetRequest { constructor( uuid: string, href: string, - public body?: FindAllOptions, + public body?: FindListOptions, ) { super(uuid, href); } diff --git a/src/app/core/data/resource-policy.service.ts b/src/app/core/data/resource-policy.service.ts index 1574111232..017e5cf5ee 100644 --- a/src/app/core/data/resource-policy.service.ts +++ b/src/app/core/data/resource-policy.service.ts @@ -6,7 +6,7 @@ import { Observable } from 'rxjs'; import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { ResourcePolicy } from '../shared/resource-policy.model'; import { RemoteData } from '../data/remote-data'; @@ -36,7 +36,7 @@ class DataServiceImpl extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } } diff --git a/src/app/core/eperson/eperson.service.ts b/src/app/core/eperson/eperson.service.ts index 70ecf3f59e..81ae532e3b 100644 --- a/src/app/core/eperson/eperson.service.ts +++ b/src/app/core/eperson/eperson.service.ts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { DataService } from '../data/data.service'; import { CacheableObject } from '../cache/object-cache.reducer'; @@ -8,7 +8,7 @@ import { CacheableObject } from '../cache/object-cache.reducer'; */ export abstract class EpersonService extends DataService { - public getBrowseEndpoint(options: FindAllOptions): Observable { + public getBrowseEndpoint(options: FindListOptions): Observable { return this.halService.getEndpoint(this.linkPath); } } diff --git a/src/app/core/eperson/group-eperson.service.ts b/src/app/core/eperson/group-eperson.service.ts index 2014e6120a..c8a2a78917 100644 --- a/src/app/core/eperson/group-eperson.service.ts +++ b/src/app/core/eperson/group-eperson.service.ts @@ -7,7 +7,7 @@ import { filter, map, take } from 'rxjs/operators'; import { EpersonService } from './eperson.service'; import { RequestService } from '../data/request.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { Group } from './models/group.model'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -52,7 +52,7 @@ export class GroupEpersonService extends EpersonService { */ isMemberOf(groupName: string): Observable { const searchHref = 'isMemberOf'; - const options = new FindAllOptions(); + const options = new FindListOptions(); options.searchParams = [new SearchParam('groupName', groupName)]; return this.searchBy(searchHref, options).pipe( diff --git a/src/app/core/submission/workflowitem-data.service.ts b/src/app/core/submission/workflowitem-data.service.ts index 43c4aecafe..47195ed0a1 100644 --- a/src/app/core/submission/workflowitem-data.service.ts +++ b/src/app/core/submission/workflowitem-data.service.ts @@ -8,7 +8,7 @@ import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; import { WorkflowItem } from './models/workflowitem.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -35,7 +35,7 @@ export class WorkflowItemDataService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions) { + public getBrowseEndpoint(options: FindListOptions) { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/submission/workspaceitem-data.service.ts b/src/app/core/submission/workspaceitem-data.service.ts index 4d388ec513..3f782b74a2 100644 --- a/src/app/core/submission/workspaceitem-data.service.ts +++ b/src/app/core/submission/workspaceitem-data.service.ts @@ -7,7 +7,7 @@ import { CoreState } from '../core.reducers'; import { DataService } from '../data/data.service'; import { RequestService } from '../data/request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindAllOptions } from '../data/request.models'; +import { FindListOptions } from '../data/request.models'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { ObjectCacheService } from '../cache/object-cache.service'; @@ -35,7 +35,7 @@ export class WorkspaceitemDataService extends DataService { super(); } - public getBrowseEndpoint(options: FindAllOptions) { + public getBrowseEndpoint(options: FindListOptions) { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/core/tasks/tasks.service.ts b/src/app/core/tasks/tasks.service.ts index f39b144c6a..cf23bfd74b 100644 --- a/src/app/core/tasks/tasks.service.ts +++ b/src/app/core/tasks/tasks.service.ts @@ -4,7 +4,7 @@ import { merge as observableMerge, Observable, of as observableOf } from 'rxjs'; import { distinctUntilChanged, filter, flatMap, map, mergeMap, tap } from 'rxjs/operators'; import { DataService } from '../data/data.service'; -import { DeleteRequest, FindAllOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; +import { DeleteRequest, FindListOptions, PostRequest, TaskDeleteRequest, TaskPostRequest } from '../data/request.models'; import { isNotEmpty } from '../../shared/empty.util'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { ProcessTaskResponse } from './models/process-task-response'; @@ -18,7 +18,7 @@ import { CacheableObject } from '../cache/object-cache.reducer'; */ export abstract class TasksService extends DataService { - public getBrowseEndpoint(options: FindAllOptions): Observable { + public getBrowseEndpoint(options: FindListOptions): Observable { return this.halService.getEndpoint(this.linkPath); } diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index 79d2f2a7bc..f84764d6a4 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -36,7 +36,7 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { CollectionDataService } from '../../../core/data/collection-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; /** * An interface to represent a collection entry @@ -205,7 +205,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { map((collectionRD: RemoteData) => collectionRD.payload.name) ); - const findOptions: FindAllOptions = { + const findOptions: FindListOptions = { elementsPerPage: 1000 }; From ee140e623a64c7bebf61a0984525faa7c7217243 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 13:02:24 +0200 Subject: [PATCH 23/46] 65600: Cleanup & indentation of shortDescription --- .../community-list.component.html | 26 ++++++++++++------- .../community-list.component.ts | 3 +-- src/app/core/data/community-data.service.ts | 1 - 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index 77e9e1ee40..a70de8475a 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -6,7 +6,6 @@
+
+
+ + {{node.payload.shortDescription}} +
+
+ class="example-tree-node">
-
+
{{node.payload.shortDescription}}
@@ -46,18 +54,18 @@ [attr.aria-label]="'toggle ' + node.name" (click)="toggleExpanded(node)" [ngClass]="node.isExpandable ? 'visible' : 'invisible'"> - +
- {{node.name}} {{node.id}} + {{node.name}}
-
+
{{node.payload.shortDescription}}
diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index c45e5098ab..5cce255380 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -4,7 +4,7 @@ import {CommunityListDatasource} from '../community-list-datasource'; import {FlatTreeControl} from '@angular/cdk/tree'; import {Collection} from '../../core/shared/collection.model'; import {Community} from '../../core/shared/community.model'; -import {isEmpty} from "../../shared/empty.util"; +import {isEmpty} from '../../shared/empty.util'; @Component({ selector: 'ds-community-list', @@ -14,7 +14,6 @@ import {isEmpty} from "../../shared/empty.util"; export class CommunityListComponent implements OnInit { private expandedNodes: FlatNode[] = []; - private Arr = Array; treeControl = new FlatTreeControl( (node) => node.level, (node) => node.isExpandable diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 1e3875edd4..7368d30202 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -59,7 +59,6 @@ export class CommunityDataService extends ComColDataService { findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); - console.log('subcomurl', hrefObs.pipe(take(1)).subscribe((val) => console.log('subcomurl', val))); hrefObs.pipe( filter((href: string) => hasValue(href)), From aed4db6289f8b62a16064a295f87c894da947450 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 21 Aug 2019 17:31:24 +0200 Subject: [PATCH 24/46] 64387: HAL endpoint refactoring --- src/app/core/data/comcol-data.service.ts | 6 ++++++ src/app/core/data/item-data.service.ts | 10 ++++++++++ src/app/core/shared/hal-endpoint.service.ts | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 62e2acfa87..49c5c908ef 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -6,6 +6,8 @@ import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; import { DataService } from './data.service'; +import { PaginatedList } from './paginated-list'; +import { RemoteData } from './remote-data'; import { FindListOptions, FindByIDRequest } from './request.models'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { getResponseFromEntry } from '../shared/operators'; @@ -57,4 +59,8 @@ export abstract class ComColDataService extends DataS return observableMerge(errorResponses, successResponses).pipe(distinctUntilChanged(), share()); } } + + public findByParentCommunity(parentUUID: string): Observable>> { + this.halService.getEndpoint(`communities/${parentUUID}/collections`) + } } diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts index affcae619c..b729c0fafe 100644 --- a/src/app/core/data/item-data.service.ts +++ b/src/app/core/data/item-data.service.ts @@ -247,4 +247,14 @@ export class ItemDataService extends DataService { map((request: RequestEntry) => request.response) ); } + + /** + * Get the endpoint for an item's bitstreams + * @param itemId + */ + public getBitstreamsEndpoint(itemId: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + switchMap((url: string) => this.halService.getEndpoint('bitstreams', `${url}/${itemId}`)) + ); + } } diff --git a/src/app/core/shared/hal-endpoint.service.ts b/src/app/core/shared/hal-endpoint.service.ts index a93d54db64..117cc074ca 100644 --- a/src/app/core/shared/hal-endpoint.service.ts +++ b/src/app/core/shared/hal-endpoint.service.ts @@ -43,8 +43,8 @@ export class HALEndpointService { ); } - public getEndpoint(linkPath: string): Observable { - return this.getEndpointAt(this.getRootHref(), ...linkPath.split('/')); + public getEndpoint(linkPath: string, startHref?: string): Observable { + return this.getEndpointAt(startHref || this.getRootHref(), ...linkPath.split('/')); } /** From adfe881a817aae1a32b644848e30a7553e338b70 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 16 Oct 2019 13:12:33 +0200 Subject: [PATCH 25/46] add findByParent method to ComColDataService --- src/app/core/data/collection-data.service.ts | 8 +++++++- src/app/core/data/comcol-data.service.ts | 21 ++++++++++++++++---- src/app/core/data/community-data.service.ts | 16 +++++++++++---- src/app/core/data/data.service.ts | 8 ++++---- src/app/core/data/request.models.ts | 2 +- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 88cc80a9a8..2e1eac9e9c 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; -import { distinctUntilChanged, filter, map, take } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -136,4 +136,10 @@ export class CollectionDataService extends ComColDataService { return this.rdbService.buildList(href$); } + protected getFindByParentHref(parentUUID: string): Observable { + return this.halService.getEndpoint('communities').pipe( + switchMap((communityEndpointHref: string) => + this.halService.getEndpoint('collections', `${communityEndpointHref}/${parentUUID}`)), + ); + } } diff --git a/src/app/core/data/comcol-data.service.ts b/src/app/core/data/comcol-data.service.ts index 49c5c908ef..867ee24fc1 100644 --- a/src/app/core/data/comcol-data.service.ts +++ b/src/app/core/data/comcol-data.service.ts @@ -1,6 +1,15 @@ -import { distinctUntilChanged, filter, map, mergeMap, share, take, tap } from 'rxjs/operators'; +import { + distinctUntilChanged, + filter, first, + map, + mergeMap, + share, + switchMap, + take, + tap +} from 'rxjs/operators'; import { merge as observableMerge, Observable, throwError as observableThrowError } from 'rxjs'; -import { isEmpty, isNotEmpty } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util'; import { NormalizedCommunity } from '../cache/models/normalized-community.model'; import { ObjectCacheService } from '../cache/object-cache.service'; import { CommunityDataService } from './community-data.service'; @@ -60,7 +69,11 @@ export abstract class ComColDataService extends DataS } } - public findByParentCommunity(parentUUID: string): Observable>> { - this.halService.getEndpoint(`communities/${parentUUID}/collections`) + protected abstract getFindByParentHref(parentUUID: string): Observable; + + public findByParent(parentUUID: string, options: FindListOptions = {}): Observable>> { + const href$ = this.buildHrefFromFindOptions(this.getFindByParentHref(parentUUID), [], options); + return this.findList(href$, options); } + } diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index 7368d30202..d28c975122 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -1,4 +1,4 @@ -import { filter, take } from 'rxjs/operators'; +import { filter, switchMap, take } from 'rxjs/operators'; import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; @@ -9,7 +9,7 @@ import { Community } from '../shared/community.model'; import { ComColDataService } from './comcol-data.service'; import { RequestService } from './request.service'; import { HALEndpointService } from '../shared/hal-endpoint.service'; -import { FindListOptions, FindAllRequest } from './request.models'; +import { FindListOptions, FindListRequest } from './request.models'; import { RemoteData } from './remote-data'; import { hasValue } from '../../shared/empty.util'; import { Observable } from 'rxjs'; @@ -50,7 +50,7 @@ export class CommunityDataService extends ComColDataService { filter((href: string) => hasValue(href)), take(1)) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); }); @@ -64,10 +64,18 @@ export class CommunityDataService extends ComColDataService { filter((href: string) => hasValue(href)), take(1)) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); this.requestService.configure(request); }); return this.rdbService.buildList(hrefObs) as Observable>>; } + + protected getFindByParentHref(parentUUID: string): Observable { + return this.halService.getEndpoint(this.linkPath).pipe( + switchMap((communityEndpointHref: string) => + this.halService.getEndpoint('subcommunities', `${communityEndpointHref}/${parentUUID}`)) + ); + } + } diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 69b2cdfcb5..2518de9a7b 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -15,7 +15,7 @@ import { CreateRequest, DeleteByIDRequest, FindListOptions, - FindAllRequest, + FindListRequest, FindByIDRequest, GetRequest } from './request.models'; @@ -133,7 +133,7 @@ export abstract class DataService { hrefObs.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); if (hasValue(this.responseMsToLive)) { request.responseMsToLive = this.responseMsToLive; } @@ -191,7 +191,7 @@ export abstract class DataService { } /** - * Make a new FindAllRequest with given search method + * Make a new FindListRequest with given search method * * @param searchMethod The search method for the object * @param options The [[FindListOptions]] object @@ -205,7 +205,7 @@ export abstract class DataService { hrefObs.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { - const request = new FindAllRequest(this.requestService.generateRequestId(), href, options); + const request = new FindListRequest(this.requestService.generateRequestId(), href, options); request.responseMsToLive = 10 * 1000; this.requestService.configure(request); }); diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts index 6396d7a374..ca864f99de 100644 --- a/src/app/core/data/request.models.ts +++ b/src/app/core/data/request.models.ts @@ -147,7 +147,7 @@ export class FindListOptions { startsWith?: string; } -export class FindAllRequest extends GetRequest { +export class FindListRequest extends GetRequest { constructor( uuid: string, href: string, From 84326ef5646322f3221e80fc02b550278c5f4c1a Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 16 Oct 2019 15:00:46 +0200 Subject: [PATCH 26/46] Pagination for collections & Process feedback --- resources/i18n/en.json5 | 1 + .../community-list-adapter.ts | 225 ------- .../community-list-datasource.ts | 48 +- .../community-list-page.component.html | 4 +- .../community-list-page.component.scss | 0 .../community-list-page.component.spec.ts | 28 +- .../community-list-page.component.ts | 10 +- .../community-list-page.module.ts | 26 +- .../community-list-page.routing.module.ts | 28 +- .../community-list-service.spec.ts | 560 ++++++++++++++++++ .../community-list-service.ts | 288 +++++++++ .../community-list.component.html | 157 ++--- .../community-list.component.scss | 0 .../community-list.component.spec.ts | 313 +++++++++- .../community-list.component.ts | 134 +++-- src/app/core/data/collection-data.service.ts | 2 +- src/app/core/data/comcol-data.service.spec.ts | 7 +- src/app/core/data/community-data.service.ts | 15 - src/app/core/data/data.service.ts | 8 +- src/app/navbar/navbar.component.ts | 23 +- 20 files changed, 1420 insertions(+), 457 deletions(-) delete mode 100644 src/app/community-list-page/community-list-page.component.scss create mode 100644 src/app/community-list-page/community-list-service.spec.ts create mode 100644 src/app/community-list-page/community-list-service.ts delete mode 100644 src/app/community-list-page/community-list/community-list.component.scss diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7ed08ac732..5b3747caf6 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -343,6 +343,7 @@ "communityList.tabTitle": "DSpace - Community List", "communityList.title": "List of Communities", + "communityList.showMore": "Show More", diff --git a/src/app/community-list-page/community-list-adapter.ts b/src/app/community-list-page/community-list-adapter.ts index 541a5d0189..e69de29bb2 100644 --- a/src/app/community-list-page/community-list-adapter.ts +++ b/src/app/community-list-page/community-list-adapter.ts @@ -1,225 +0,0 @@ -import {Injectable} from '@angular/core'; -import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {merge, Observable, of, of as observableOf} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, defaultIfEmpty, filter, map, switchMap, take, tap} from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; -import {Collection} from '../core/shared/collection.model'; -import {hasValue, isEmpty, isNotEmpty} from '../shared/empty.util'; -import {RemoteData} from '../core/data/remote-data'; -import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; -import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; -import {CollectionDataService} from '../core/data/collection-data.service'; - -export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; -} - -export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); - -export const toFlatNode = ( - c: Community | Collection, - level: number, - isExpanded: boolean, - parent?: FlatNode -): FlatNode => ({ - isExpandable: c instanceof Community, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), -}); - -export const showMoreFlatNode = ( - c: Community | Collection, - level: number, - parent?: FlatNode -): FlatNode => ({ - isExpandable: false, - name: c.name, - id: c.id, - level: level, - isExpanded: false, - parent: parent, - payload: c, - isShowMoreNode: true, -}); - -@Injectable() -export class CommunityListAdapter { - - payloads$: Array>>; - - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; - - maxSubCommunitiesPerPage: number; - - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList() - - this.maxSubCommunitiesPerPage = 3; - } - - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} - }).pipe( - take(1), - map((results) => results.payload), - )]; - - } - - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: {field: this.topCommunitiesSortConfig.field, direction: this.topCommunitiesSortConfig.direction} - }).pipe( - take(1), - map((results) => results.payload), - )]; - } - - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); - }); - return combineAndFlatten(res); - }; - - private transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) - }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, this.addPossibleShowMoreComunityFlatNode(level, parent)]; - } - - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - private transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const communityFlatNode = toFlatNode(community, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms$ = []; - for (let i = 1; i <= currentPage ; i++) { - const p = this.communityDataService.findSubCommunitiesPerParentCommunity(community.uuid,{elementsPerPage: this.maxSubCommunitiesPerPage, currentPage: i}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - - ); - subcoms$ = [...subcoms$, p]; - } - - obsList = [...obsList, combineAndFlatten(subcoms$)]; - - // need to be authorized (logged in) to receive collections this way - // const cols = this.collectionDataService.getAuthorizedCollectionByCommunity(community.uuid,{elementsPerPage: 2}); - // cols.pipe(take(1)).subscribe((val) => console.log('cols:', val)); - - const collectionNodes$ = community.collections.pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => { - let nodes$ = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, level + 1, false, parent)); - if (rd.payload.elementsPerPage < rd.payload.totalElements) { - nodes$ = [...nodes$, this.addPossibleShowMoreCollectionFlatNode(level + 1, parent)]; - } - return nodes$; - } - ) - ); - obsList = [...obsList, collectionNodes$]; - } - - return combineAndFlatten(obsList); - } - - private addPossibleShowMoreComunityFlatNode(level: number, parent: FlatNode): Observable { - const dummyCommunity = Object.assign(new Community(), { - id: '999999', - metadata: { - 'dc.title': [ - { language: 'en_US', value: 'Test' } - ] - } - }) - return of([showMoreFlatNode(dummyCommunity, level, parent)]); - } - - private addPossibleShowMoreCollectionFlatNode(level: number, parent: FlatNode): FlatNode { - const dummyCollection = Object.assign(new Collection(), { - id: '999999', - metadata: { - 'dc.title': [ - { language: 'en_US', value: 'Test' } - ] - } - }) - return showMoreFlatNode(dummyCollection, level, parent); - } - -} diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts index eab377f970..da4bcc00cd 100644 --- a/src/app/community-list-page/community-list-datasource.ts +++ b/src/app/community-list-page/community-list-datasource.ts @@ -1,33 +1,35 @@ -import {CommunityListAdapter, FlatNode} from './community-list-adapter'; -import {CollectionViewer, DataSource} from '@angular/cdk/typings/collections'; -import {BehaviorSubject, Observable,} from 'rxjs'; -import {finalize, take,} from 'rxjs/operators'; +import { CommunityListService, FlatNode } from './community-list-service'; +import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; +import { BehaviorSubject, Observable, } from 'rxjs'; +import { finalize, take, } from 'rxjs/operators'; export class CommunityListDatasource implements DataSource { - private communityList$ = new BehaviorSubject([]); - private loading$ = new BehaviorSubject(false); + private communityList$ = new BehaviorSubject([]); + public loading$ = new BehaviorSubject(false); - constructor(private communityListService: CommunityListAdapter) { - } + constructor(private communityListService: CommunityListService) { + } - connect(collectionViewer: CollectionViewer): Observable { - this.loadCommunities(null); - return this.communityList$.asObservable(); - } + connect(collectionViewer: CollectionViewer): Observable { + this.loadCommunities(null); + return this.communityList$.asObservable(); + } - loadCommunities(expandedNodes: FlatNode[]) { - this.loading$.next(true); + loadCommunities(expandedNodes: FlatNode[]) { + this.loading$.next(true); - this.communityListService.loadCommunities(expandedNodes).pipe( - take(1), - finalize(() => this.loading$.next(false)), - ).subscribe((flatNodes: FlatNode[]) => this.communityList$.next(flatNodes)); - } + this.communityListService.loadCommunities(expandedNodes).pipe( + take(1), + finalize(() => this.loading$.next(false)), + ).subscribe((flatNodes: FlatNode[]) => { + this.communityList$.next(flatNodes); + }); + } - disconnect(collectionViewer: CollectionViewer): void { - this.communityList$.complete(); - this.loading$.complete(); - } + disconnect(collectionViewer: CollectionViewer): void { + this.communityList$.complete(); + this.loading$.complete(); + } } diff --git a/src/app/community-list-page/community-list-page.component.html b/src/app/community-list-page/community-list-page.component.html index bba67a7c6d..08accdc0e5 100644 --- a/src/app/community-list-page/community-list-page.component.html +++ b/src/app/community-list-page/community-list-page.component.html @@ -1,4 +1,4 @@
-

{{ 'communityList.title' | translate }}

- +

{{ 'communityList.title' | translate }}

+
diff --git a/src/app/community-list-page/community-list-page.component.scss b/src/app/community-list-page/community-list-page.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/community-list-page/community-list-page.component.spec.ts b/src/app/community-list-page/community-list-page.component.spec.ts index 68a98276a2..0aa4afce7f 100644 --- a/src/app/community-list-page/community-list-page.component.spec.ts +++ b/src/app/community-list-page/community-list-page.component.spec.ts @@ -1,6 +1,9 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing'; import { CommunityListPageComponent } from './community-list-page.component'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; describe('CommunityListPageComponent', () => { let component: CommunityListPageComponent; @@ -8,9 +11,21 @@ describe('CommunityListPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ CommunityListPageComponent ] + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + }, + }), + ], + declarations: [CommunityListPageComponent], + providers: [ + CommunityListPageComponent, + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { @@ -19,7 +34,8 @@ describe('CommunityListPageComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); - }); + it('should create', inject([CommunityListPageComponent], (comp: CommunityListPageComponent) => { + expect(comp).toBeTruthy(); + })); + }); diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts index 76fc96dcbe..9ba715c20a 100644 --- a/src/app/community-list-page/community-list-page.component.ts +++ b/src/app/community-list-page/community-list-page.component.ts @@ -1,15 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'ds-community-list-page', templateUrl: './community-list-page.component.html', - styleUrls: ['./community-list-page.component.scss'] }) -export class CommunityListPageComponent implements OnInit { - - constructor() { } - - ngOnInit() { - } +export class CommunityListPageComponent { } diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 31a7c4e51a..817e492bf7 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -1,22 +1,22 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; -import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListPageRoutingModule} from './community-list-page.routing.module'; +import { CommunityListPageComponent } from './community-list-page.component'; +import { CommunityListPageRoutingModule } from './community-list-page.routing.module'; import { CommunityListComponent } from './community-list/community-list.component'; -import {CdkTreeModule} from '@angular/cdk/tree'; +import { CdkTreeModule } from '@angular/cdk/tree'; @NgModule({ - imports: [ - CommonModule, - SharedModule, - CommunityListPageRoutingModule, - CdkTreeModule, - ], - declarations: [ - CommunityListPageComponent, - CommunityListComponent - ] + imports: [ + CommonModule, + SharedModule, + CommunityListPageRoutingModule, + CdkTreeModule, + ], + declarations: [ + CommunityListPageComponent, + CommunityListComponent + ] }) export class CommunityListPageModule { diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index 90cd355bc5..b226236af1 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -1,17 +1,23 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import {CdkTreeModule} from '@angular/cdk/tree'; +import { CdkTreeModule } from '@angular/cdk/tree'; -import {CommunityListPageComponent} from './community-list-page.component'; -import {CommunityListAdapter} from './community-list-adapter'; +import { CommunityListPageComponent } from './community-list-page.component'; +import { CommunityListService } from './community-list-service'; @NgModule({ - imports: [ - RouterModule.forChild([ - { path: '', component: CommunityListPageComponent, pathMatch: 'full', data: { title: 'communityList.tabTitle' } } - ]), - CdkTreeModule, - ], - providers: [CommunityListAdapter] + imports: [ + RouterModule.forChild([ + { + path: '', + component: CommunityListPageComponent, + pathMatch: 'full', + data: { title: 'communityList.tabTitle' } + } + ]), + CdkTreeModule, + ], + providers: [CommunityListService] }) -export class CommunityListPageRoutingModule { } +export class CommunityListPageRoutingModule { +} diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts new file mode 100644 index 0000000000..c3cb24cc59 --- /dev/null +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -0,0 +1,560 @@ +import { TestBed, inject, async, fakeAsync } from '@angular/core/testing'; +import { CommunityListService, FlatNode, toFlatNode } from './community-list-service'; +import { CollectionDataService } from '../core/data/collection-data.service'; +import { PaginatedList } from '../core/data/paginated-list'; +import { PageInfo } from '../core/shared/page-info.model'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { + createFailedRemoteDataObject$, + createSuccessfulRemoteDataObject$ +} from '../shared/testing/utils'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { map, take } from 'rxjs/operators'; +import { FindListOptions } from '../core/data/request.models'; + +describe('CommunityListService', () => { + const standardElementsPerPage = 2; + let collectionDataServiceStub: any; + let communityDataServiceStub: any; + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + }), + Object.assign(new Community(), { + id: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + }) + ]; + const mockCollectionsPage1 = [ + Object.assign(new Collection(), { + id: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + name: 'Collection 1' + }), + Object.assign(new Collection(), { + id: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + name: 'Collection 2' + }) + ]; + const mockCollectionsPage2 = [ + Object.assign(new Collection(), { + id: 'a5159760-f362-4659-9e81-e3253ad91ede', + uuid: 'a5159760-f362-4659-9e81-e3253ad91ede', + name: 'Collection 3' + }), + Object.assign(new Collection(), { + id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + name: 'Collection 4' + }) + ]; + const mockListOfTopCommunitiesPage1 = [ + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + }), + Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + ]; + const mockListOfTopCommunitiesPage2 = [ + Object.assign(new Community(), { + id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + }), + ]; + const mockTopCommunitiesWithChildrenArraysPage1 = [ + { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: mockSubcommunities1Page1, + collections: [], + }, + { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: [], + collections: [...mockCollectionsPage1, ...mockCollectionsPage2], + }, + { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: [], + collections: [], + }]; + const mockTopCommunitiesWithChildrenArraysPage2 = [ + { + id: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + uuid: 'c2e04392-3b8a-4dfa-976d-d76fb1b8a4b6', + subcommunities: [], + collections: [], + }]; + + const allCommunities = [...mockTopCommunitiesWithChildrenArraysPage1, ...mockTopCommunitiesWithChildrenArraysPage2, ...mockSubcommunities1Page1]; + + let service: CommunityListService; + + beforeEach(async(() => { + communityDataServiceStub = { + findTop(options: FindListOptions = {}) { + const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2] + let currentPage = options.currentPage; + const elementsPerPage = 3; + if (currentPage === undefined) { + currentPage = 1 + } + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > allTopComs.length) { + endPageIndex = allTopComs.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), allTopComs.slice(startPageIndex, endPageIndex))); + }, + findByParent(parentUUID: string, options: FindListOptions = {}) { + const foundCom = allCommunities.find((community) => (community.id === parentUUID)); + let currentPage = options.currentPage; + let elementsPerPage = options.elementsPerPage; + if (currentPage === undefined) { + currentPage = 1 + } + if (elementsPerPage === 0) { + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.subcommunities as [Community]))); + } + elementsPerPage = standardElementsPerPage; + if (foundCom !== undefined && foundCom.subcommunities !== undefined) { + const coms = foundCom.subcommunities as [Community]; + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > coms.length) { + endPageIndex = coms.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), coms.slice(startPageIndex, endPageIndex))); + } else { + return createFailedRemoteDataObject$(); + } + } + }; + collectionDataServiceStub = { + findByParent(parentUUID: string, options: FindListOptions = {}) { + const foundCom = allCommunities.find((community) => (community.id === parentUUID)); + let currentPage = options.currentPage; + let elementsPerPage = options.elementsPerPage; + if (currentPage === undefined) { + currentPage = 1 + } + if (elementsPerPage === 0) { + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), (foundCom.collections as [Collection]))); + } + elementsPerPage = standardElementsPerPage; + if (foundCom !== undefined && foundCom.collections !== undefined) { + const colls = foundCom.collections as [Collection]; + const startPageIndex = (currentPage - 1) * elementsPerPage; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex > colls.length) { + endPageIndex = colls.length; + } + return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), colls.slice(startPageIndex, endPageIndex))); + } else { + return createFailedRemoteDataObject$(); + } + } + }; + TestBed.configureTestingModule({ + providers: [CommunityListService, + { provide: CollectionDataService, useValue: collectionDataServiceStub }, + { provide: CommunityDataService, useValue: communityDataServiceStub },], + }); + service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub); + })); + + afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub)); + + it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => { + expect(serviceIn).toBeTruthy(); + })); + + describe('getNextPageTopCommunities', () => { + describe('also load in second page of top communities', () => { + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the test top communities page 1 and 2', () => { + let findTopSpy; + beforeEach(() => { + findTopSpy = spyOn(communityDataServiceStub, 'findTop').and.callThrough(); + service.getNextPageTopCommunities(); + + const sub = service.loadCommunities(null) + .subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('flatnode list should contain just flatnodes of top community list page 1 and 2', () => { + expect(findTopSpy).toHaveBeenCalled(); + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockListOfTopCommunitiesPage2.length); + mockListOfTopCommunitiesPage1.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + mockListOfTopCommunitiesPage2.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + describe('loadCommunities', () => { + describe('should transform all communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => { + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the test top communities', () => { + beforeEach(() => { + const sub = service.loadCommunities(null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length); + }); + it('flatnode list should contain flatNode representations of top communities', () => { + mockListOfTopCommunitiesPage1.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + it('none of the flatnodes in the list should be expanded', () => { + flatNodeList.map((flatnode: FlatNode) => { + expect(flatnode.isExpanded).toEqual(false); + }); + }); + }); + describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const expandedNodes = []; + mockListOfTopCommunitiesPage1.map((community: Community) => { + const communityFlatNode = toFlatNode(community, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + expandedNodes.push(communityFlatNode); + }); + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of its possible page-limited children', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + describe('Just first top comm expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of page-limited children of first top community', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + }); + }); + describe('Just second top comm expanded, collections at page 2: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], true, 0, true, null); + communityFlatNode.currentCollectionPage = 2; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.loadCommunities(expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as top community list and size of page-limited children of second top community', () => { + expect(flatNodeList.length).toEqual(mockListOfTopCommunitiesPage1.length + mockCollectionsPage1.length + mockCollectionsPage2.length); + }); + it('flatnode list should contain flatNode representations of all page-limited children of first top community', () => { + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + mockCollectionsPage2.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + describe('transformListOfCommunities', () => { + describe('should transform list of communities in a list of flatnodes with possible subcoms and collections as subflatnodes if they\'re expanded', () => { + describe('list of communities with possible children', () => { + const listOfCommunities = mockListOfTopCommunitiesPage1; + let flatNodeList; + describe('None expanded: should return list containing only flatnodes of the communities in the test list', () => { + beforeEach(() => { + const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as community test list', () => { + expect(flatNodeList.length).toEqual(listOfCommunities.length); + }); + it('flatnode list should contain flatNode representations of all communities from test list', () => { + listOfCommunities.map((community) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === community.id))).toBeTruthy(); + }); + }); + it('none of the flatnodes in the list should be expanded', () => { + flatNodeList.map((flatnode: FlatNode) => { + expect(flatnode.isExpanded).toEqual(false); + }); + }); + }); + describe('All top expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { + beforeEach(() => { + const expandedNodes = []; + listOfCommunities.map((community: Community) => { + const communityFlatNode = toFlatNode(community, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + expandedNodes.push(communityFlatNode); + }); + const sub = service.transformListOfCommunities(new PaginatedList(new PageInfo(), listOfCommunities), 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be as big as community test list and size of its possible children', () => { + expect(flatNodeList.length).toEqual(listOfCommunities.length + mockSubcommunities1Page1.length + mockSubcommunities1Page1.length); + }); + it('flatnode list should contain flatNode representations of all children', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + mockSubcommunities1Page1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + }); + }); + }); + }); + + }); + + describe('transformCommunity', () => { + describe('should transform community in list of flatnodes with possible subcoms and collections as subflatnodes if its expanded', () => { + describe('topcommunity without subcoms or collections, unexpanded', () => { + const communityWithNoSubcomsOrColls = Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 2' }] + } + }); + let flatNodeList; + describe('should return list containing only flatnode corresponding to that community', () => { + beforeEach(() => { + const sub = service.transformCommunity(communityWithNoSubcomsOrColls, 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be 1', () => { + expect(flatNodeList.length).toEqual(1); + }); + it('flatnode list only element should be flatNode of test community', () => { + expect(flatNodeList[0].id).toEqual(communityWithNoSubcomsOrColls.id); + }); + it('flatnode from test community is not expanded', () => { + expect(flatNodeList[0].isExpanded).toEqual(false); + }); + }); + }); + describe('topcommunity with subcoms or collections, unexpanded', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + describe('should return list containing only flatnode corresponding to that community', () => { + beforeAll(() => { + const sub = service.transformCommunity(communityWithSubcoms, 0, null, null) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('length of flatnode list should be 1', () => { + expect(flatNodeList.length).toEqual(1); + }); + it('flatnode list only element should be flatNode of test community', () => { + expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id); + }); + it('flatnode from test community is not expanded', () => { + expect(flatNodeList[0].isExpanded).toEqual(false); + }); + }); + }); + describe('topcommunity with subcoms, expanded, first page for all', () => { + describe('should return list containing flatnodes of that community, its possible subcommunities and its possible collections', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + beforeEach(() => { + const communityFlatNode = toFlatNode(communityWithSubcoms, true, 0, true, null); + communityFlatNode.currentCollectionPage = 1; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.transformCommunity(communityWithSubcoms, 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('list of flatnodes is length is 1 + nrOfSubcoms & first flatnode is of expanded test community', () => { + expect(flatNodeList.length).toEqual(1 + mockSubcommunities1Page1.length); + expect(flatNodeList[0].isExpanded).toEqual(true); + expect(flatNodeList[0].id).toEqual(communityWithSubcoms.id); + }); + it('list of flatnodes contains flatnodes for all subcoms of test community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).toBeTruthy(); + }); + }); + it('the subcoms of the test community are a level higher than the parent community', () => { + mockSubcommunities1Page1.map((subcommunity) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === subcommunity.id))).level).toEqual(flatNodeList[0].level + 1); + }); + }); + }); + }); + describe('topcommunity with collections, expanded, on second page of collections', () => { + describe('should return list containing flatnodes of that community, its collections of the first two pages', () => { + const communityWithCollections = Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + let flatNodeList; + beforeEach(() => { + const communityFlatNode = toFlatNode(communityWithCollections, true, 0, true, null); + communityFlatNode.currentCollectionPage = 2; + communityFlatNode.currentCommunityPage = 1; + const expandedNodes = [communityFlatNode]; + const sub = service.transformCommunity(communityWithCollections, 0, null, expandedNodes) + .pipe(take(1)).subscribe((value) => flatNodeList = value); + sub.unsubscribe(); + }); + it('list of flatnodes is length is 1 + nrOfCollections & first flatnode is of expanded test community', () => { + expect(flatNodeList.length).toEqual(1 + mockCollectionsPage1.length + mockCollectionsPage2.length); + expect(flatNodeList[0].isExpanded).toEqual(true); + expect(flatNodeList[0].id).toEqual(communityWithCollections.id); + }); + it('list of flatnodes contains flatnodes for all subcolls (first 2 pages) of test community', () => { + mockCollectionsPage1.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }); + mockCollectionsPage2.map((collection) => { + expect(flatNodeList.find((flatnode) => (flatnode.id === collection.id))).toBeTruthy(); + }) + }); + it('the collections of the test community are a level higher than the parent community', () => { + mockCollectionsPage1.map((collection) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1); + }); + mockCollectionsPage2.map((collection) => { + expect((flatNodeList.find((flatnode) => (flatnode.id === collection.id))).level).toEqual(flatNodeList[0].level + 1); + }) + }); + }); + }); + }); + + }); + + describe('getIsExpandable', () => { + describe('should return true', () => { + it('if community has subcommunities', () => { + const communityWithSubcoms = Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: '2 subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 1' }] + } + }); + expect(service.getIsExpandable(communityWithSubcoms)).toEqual(true); + }); + it('if community has collections', () => { + const communityWithCollections = Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockCollectionsPage1)), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, 2 coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 2' }] + } + }); + expect(service.getIsExpandable(communityWithCollections)).toEqual(true); + }); + }); + describe('should return false', () => { + it('if community has neither subcommunities nor collections', () => { + const communityWithNoSubcomsOrColls = Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + metadata: { + 'dc.description': [{ language: 'en_US', value: 'no subcoms, no coll' }], + 'dc.title': [{ language: 'en_US', value: 'Community 3' }] + } + }); + expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(false); + }); + }); + + }); + +}); diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts new file mode 100644 index 0000000000..6da1bff4e5 --- /dev/null +++ b/src/app/community-list-page/community-list-service.ts @@ -0,0 +1,288 @@ +import { Injectable } from '@angular/core'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { Observable, of as observableOf } from 'rxjs'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { PaginatedList } from '../core/data/paginated-list'; +import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; +import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; +import { CollectionDataService } from '../core/data/collection-data.service'; + +export interface FlatNode { + isExpandable: boolean; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; +} + +export class ShowMoreFlatNode { +} + +export const combineAndFlatten = (obsList: Array>): Observable => + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); + +export const toFlatNode = ( + c: Community | Collection, + isExpandable: boolean, + level: number, + isExpanded: boolean, + parent?: FlatNode +): FlatNode => ({ + isExpandable: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), +}); + +export const showMoreFlatNode = ( + id: string, + level: number, + parent: FlatNode +): FlatNode => ({ + isExpandable: false, + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, +}); + +// tslint:disable-next-line:max-classes-per-file +@Injectable() +export class CommunityListService { + + // page-limited list of top-level communities + payloads$: Array>>; + + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; + + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; + + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); + + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } + + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Gets all top communities, limited by page, and transforms this in a list of flatnodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); + }); + return combineAndFlatten(res); + }; + + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } + + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const isExpandable = this.getIsExpandable(community); + + const communityFlatNode = toFlatNode(community, isExpandable, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, false, level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * If it does, it is expandable => returns true, false if not + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): boolean { + let isExpandable = true; + let nrOfSubcoms; + let nrOfColl; + this.communityDataService.findByParent(community.uuid, { elementsPerPage: 0 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + ) + .subscribe((result) => { + if (result.payload.totalElements === 0) { + nrOfSubcoms = result.payload.totalElements; + } + ; + }); + + this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 0 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + ) + .subscribe((result) => { + if (result.payload.totalElements === 0) { + nrOfColl = result.payload.totalElements; + } + ; + }); + if (nrOfSubcoms === 0 && nrOfColl === 0) { + isExpandable = false; + } + return isExpandable; + } + +} diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index a70de8475a..fdec67d67c 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -1,74 +1,91 @@ - + - - - +
+
+
+
+
+ + +
+ +
+ + {{node.name}} + +
+
+ +
+
+ + + {{node.payload.shortDescription}} + +
+
+
+
+ + +
+
+ + +
+ -
- {{ 'communityList.showMore' | translate }} -
+ +
+ + {{node.name}} + +
+
+ +
+
+ + + {{node.payload.shortDescription}} +
-
-
- - {{node.payload.shortDescription}} -
-
- - - -
- -
- - {{node.name}} - -
-
-
-
- - {{node.payload.shortDescription}} -
-
-
- - -
- -
- - {{node.name}} - -
-
-
-
- - {{node.payload.shortDescription}} -
-
-
+
+
+
diff --git a/src/app/community-list-page/community-list/community-list.component.scss b/src/app/community-list-page/community-list/community-list.component.scss deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index dadcc11251..c1c9285491 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -1,16 +1,203 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { async, ComponentFixture, fakeAsync, inject, TestBed, tick } from '@angular/core/testing'; import { CommunityListComponent } from './community-list.component'; +import { + CommunityListService, + FlatNode, + showMoreFlatNode, + toFlatNode +} from '../community-list-service'; +import { CdkTreeModule } from '@angular/cdk/tree'; +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; +import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { RouterTestingModule } from '@angular/router/testing'; +import { Community } from '../../core/shared/community.model'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; +import { PaginatedList } from '../../core/data/paginated-list'; +import { PageInfo } from '../../core/shared/page-info.model'; +import { Collection } from '../../core/shared/collection.model'; +import { Observable, of as observableOf } from 'rxjs'; +import { By } from '@angular/platform-browser'; +import { isEmpty, isNotEmpty } from '../../shared/empty.util'; describe('CommunityListComponent', () => { let component: CommunityListComponent; let fixture: ComponentFixture; - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CommunityListComponent ] + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + name: 'subcommunity1', + }), + Object.assign(new Community(), { + id: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + uuid: '59ee713b-ee53-4220-8c3f-9860dc84fe33', + name: 'subcommunity2', }) - .compileComponents(); + ]; + const mockCollectionsPage1 = [ + Object.assign(new Collection(), { + id: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + uuid: 'e9dbf393-7127-415f-8919-55be34a6e9ed', + name: 'collection1', + }), + Object.assign(new Collection(), { + id: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + uuid: '59da2ff0-9bf4-45bf-88be-e35abd33f304', + name: 'collection2', + }) + ]; + const mockCollectionsPage2 = [ + Object.assign(new Collection(), { + id: 'a5159760-f362-4659-9e81-e3253ad91ede', + uuid: 'a5159760-f362-4659-9e81-e3253ad91ede', + name: 'collection3', + }), + Object.assign(new Collection(), { + id: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + uuid: 'a392e16b-fcf2-400a-9a88-53ef7ecbdcd3', + name: 'collection4', + }) + ]; + + const mockTopCommunitiesWithChildrenArrays = [ + { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: mockSubcommunities1Page1, + collections: [], + }, + { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: [], + collections: [...mockCollectionsPage1, ...mockCollectionsPage2], + }, + { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: [], + collections: [], + }]; + + const mockTopFlatnodesUnexpanded: FlatNode[] = [ + toFlatNode( + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community1', + }), true, 0, false, null + ), + toFlatNode( + Object.assign(new Community(), { + id: '9076bd16-e69a-48d6-9e41-0238cb40d863', + uuid: '9076bd16-e69a-48d6-9e41-0238cb40d863', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), + name: 'community2', + }), true, 0, false, null + ), + toFlatNode( + Object.assign(new Community(), { + id: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + uuid: 'efbf25e1-2d8c-4c28-8f3e-2e04c215be24', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community3', + }), false, 0, false, null + ), + ]; + let communityListServiceStub; + + beforeEach(async(() => { + communityListServiceStub = { + topPageSize: 2, + topCurrentPage: 1, + collectionPageSize: 2, + subcommunityPageSize: 2, + getNextPageTopCommunities() { + this.topCurrentPage++; + }, + loadCommunities(expandedNodes) { + let flatnodes; + let showMoreTopComNode = false; + flatnodes = [...mockTopFlatnodesUnexpanded]; + const currentPage = this.topCurrentPage; + const elementsPerPage = this.topPageSize; + let endPageIndex = (currentPage * elementsPerPage); + if (endPageIndex >= flatnodes.length) { + endPageIndex = flatnodes.length; + } else { + showMoreTopComNode = true; + } + if (expandedNodes === null || isEmpty(expandedNodes)) { + if (showMoreTopComNode) { + return observableOf([...mockTopFlatnodesUnexpanded.slice(0, endPageIndex), showMoreFlatNode('community', 0, null)]); + } else { + return observableOf(mockTopFlatnodesUnexpanded.slice(0, endPageIndex)); + } + } else { + flatnodes = []; + const topFlatnodes = mockTopFlatnodesUnexpanded.slice(0, endPageIndex); + topFlatnodes.map((topNode: FlatNode) => { + flatnodes = [...flatnodes, topNode] + const expandedParent: FlatNode = expandedNodes.find((expandedNode: FlatNode) => expandedNode.id === topNode.id); + if (isNotEmpty(expandedParent)) { + const matchingTopComWithArrays = mockTopCommunitiesWithChildrenArrays.find((topcom) => topcom.id === topNode.id); + if (isNotEmpty(matchingTopComWithArrays)) { + const possibleSubcoms: Community[] = matchingTopComWithArrays.subcommunities; + let subComFlatnodes = []; + possibleSubcoms.map((subcom: Community) => { + subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, false, topNode.level + 1, false, topNode)]; + }); + const possibleColls: Collection[] = matchingTopComWithArrays.collections; + let collFlatnodes = []; + possibleColls.map((coll: Collection) => { + collFlatnodes = [...collFlatnodes, toFlatNode(coll, false, topNode.level + 1, false, topNode)]; + }); + if (isNotEmpty(subComFlatnodes)) { + const endSubComIndex = this.subcommunityPageSize * expandedParent.currentCommunityPage; + flatnodes = [...flatnodes, ...subComFlatnodes.slice(0, endSubComIndex)]; + if (subComFlatnodes.length > endSubComIndex) { + flatnodes = [...flatnodes, showMoreFlatNode('community', topNode.level + 1, expandedParent)]; + } + } + if (isNotEmpty(collFlatnodes)) { + const endColIndex = this.collectionPageSize * expandedParent.currentCollectionPage; + flatnodes = [...flatnodes, ...collFlatnodes.slice(0, endColIndex)]; + if (collFlatnodes.length > endColIndex) { + flatnodes = [...flatnodes, showMoreFlatNode('collection', topNode.level + 1, expandedParent)]; + } + } + } + } + }); + if (showMoreTopComNode) { + flatnodes = [...flatnodes, showMoreFlatNode('community', 0, null)]; + } + return observableOf(flatnodes); + } + } + }; + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + }, + }), + CdkTreeModule, + RouterTestingModule], + declarations: [CommunityListComponent], + providers: [CommunityListComponent, + { provide: CommunityListService, useValue: communityListServiceStub },], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + }) + .compileComponents(); })); beforeEach(() => { @@ -19,7 +206,119 @@ describe('CommunityListComponent', () => { fixture.detectChanges(); }); - it('should create', () => { - expect(component).toBeTruthy(); + it('should create', inject([CommunityListComponent], (comp: CommunityListComponent) => { + expect(comp).toBeTruthy(); + })); + + it('should render a cdk tree with the first elementsPerPage (2) nr of top level communities, unexpanded', () => { + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + const allNodes = [...expandableNodesFound, ...childlessNodesFound]; + expect(allNodes.length).toEqual(2); + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); }); + + it('show more node is present at end of nodetree', () => { + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(1); + expect(showMoreEl).toBeTruthy(); + }); + + describe('when show more of top communities is clicked', () => { + beforeEach(fakeAsync(() => { + const showMoreLink = fixture.debugElement.query(By.css('.show-more-node a')); + showMoreLink.triggerEventHandler('click', { + preventDefault: () => {/**/ + } + }); + tick(); + fixture.detectChanges(); + })); + it('tree contains maximum of currentPage (2) * (2) elementsPerPage of first top communities, or less if there are less communities (3)', () => { + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + const allNodes = [...expandableNodesFound, ...childlessNodesFound]; + expect(allNodes.length).toEqual(3); + mockTopFlatnodesUnexpanded.map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + }); + it('show more node is gone from end of nodetree', () => { + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(0); + }); + }); + + describe('when first expandable node is expanded', () => { + let allNodes; + beforeEach(fakeAsync(() => { + const chevronExpand = fixture.debugElement.query(By.css('.expandable-node button')); + const chevronExpandSpan = fixture.debugElement.query(By.css('.expandable-node button span')); + if (chevronExpandSpan.nativeElement.classList.contains('fa-chevron-right')) { + chevronExpand.nativeElement.click(); + tick(); + fixture.detectChanges(); + } + + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + allNodes = [...expandableNodesFound, ...childlessNodesFound]; + })); + describe('children of first expandable node are added to tree (page-limited)', () => { + it('tree contains page-limited topcoms (2) and children of first expandable node (2subcoms)', () => { + expect(allNodes.length).toEqual(4); + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + mockSubcommunities1Page1.map((subcom) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === subcom.name); + })).toBeTruthy(); + }) + }); + }); + }); + + describe('second top community node is expanded and has more children (collections) than page size of collection', () => { + describe('children of second top com are added (page-limited pageSize 2)', () => { + let allNodes; + beforeEach(fakeAsync(() => { + const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button')); + const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span')); + if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) { + chevronExpand[1].nativeElement.click(); + tick(); + fixture.detectChanges(); + } + + const expandableNodesFound = fixture.debugElement.queryAll(By.css('.expandable-node a')); + const childlessNodesFound = fixture.debugElement.queryAll(By.css('.childless-node a')); + allNodes = [...expandableNodesFound, ...childlessNodesFound]; + })); + it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => { + mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === topFlatnode.name); + })).toBeTruthy(); + }); + mockCollectionsPage1.map((coll) => { + expect(allNodes.find((foundEl) => { + return (foundEl.nativeElement.textContent.trim() === coll.name); + })).toBeTruthy(); + }) + expect(allNodes.length).toEqual(4); + const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); + expect(showMoreEl.length).toEqual(2); + }); + }); + }); + }); diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 5cce255380..622d03237e 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,76 +1,88 @@ -import {Component, OnInit} from '@angular/core'; -import {CommunityListAdapter, FlatNode} from '../community-list-adapter'; -import {CommunityListDatasource} from '../community-list-datasource'; -import {FlatTreeControl} from '@angular/cdk/tree'; -import {Collection} from '../../core/shared/collection.model'; -import {Community} from '../../core/shared/community.model'; -import {isEmpty} from '../../shared/empty.util'; +import { Component, OnInit } from '@angular/core'; +import { CommunityListService, FlatNode } from '../community-list-service'; +import { CommunityListDatasource } from '../community-list-datasource'; +import { FlatTreeControl } from '@angular/cdk/tree'; +import { isEmpty } from '../../shared/empty.util'; @Component({ - selector: 'ds-community-list', - templateUrl: './community-list.component.html', - styleUrls: ['./community-list.component.scss'] + selector: 'ds-community-list', + templateUrl: './community-list.component.html', }) export class CommunityListComponent implements OnInit { - private expandedNodes: FlatNode[] = []; + private expandedNodes: FlatNode[] = []; + public loadingNode: FlatNode; - treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable - ); + treeControl = new FlatTreeControl( + (node) => node.level, (node) => node.isExpandable + ); - dataSource: CommunityListDatasource; + dataSource: CommunityListDatasource; - constructor(private communityListAdapter: CommunityListAdapter) { + constructor(private communityListService: CommunityListService) { + } + + ngOnInit() { + this.dataSource = new CommunityListDatasource(this.communityListService); + this.dataSource.loadCommunities(null); + } + + // whether or not this node has children (subcommunities or collections) + hasChild(_: number, node: FlatNode) { + return node.isExpandable; + } + + // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections + isShowMore(_: number, node: FlatNode) { + return node.isShowMoreNode; + } + + /** + * Toggles the expanded variable of a node, adds it to the exapanded nodes list and reloads the tree so this node is expanded + * @param node Node we want to expand + */ + toggleExpanded(node: FlatNode) { + this.loadingNode = node; + if (node.isExpanded) { + this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); + node.isExpanded = false; + } else { + this.expandedNodes.push(node); + node.isExpanded = true; + if (isEmpty(node.currentCollectionPage)) { + node.currentCollectionPage = 1; + } + if (isEmpty(node.currentCommunityPage)) { + node.currentCommunityPage = 1; + } } + this.dataSource.loadCommunities(this.expandedNodes); + } - ngOnInit() { - this.dataSource = new CommunityListDatasource(this.communityListAdapter); - this.dataSource.loadCommunities(null); - } - - hasChild(_: number, node: FlatNode) { - return node.isExpandable; - } - - isShowMore(_: number, node: FlatNode) { - return node.isShowMoreNode; - } - - toggleExpanded(node: FlatNode) { - if (node.isExpanded) { - this.expandedNodes = this.expandedNodes.filter((node2) => node2.name !== node.name); - node.isExpanded = false; - } else { - this.expandedNodes.push(node); - node.isExpanded = true; - if (isEmpty(node.currentCollectionPage)) { - node.currentCollectionPage = 1; - } - if (isEmpty(node.currentCommunityPage)) { - node.currentCommunityPage = 1; - } + /** + * Makes sure the next page of a node is added to the tree (top community, sub community of collection) + * > Finds its parent (if not top community) and increases its corresponding collection/subcommunity currentPage + * > Reloads tree with new page added to corresponding top community lis, sub community list or collection list + * @param node The show more node indicating whether it's an increase in top communities, sub communities or collections + */ + getNextPage(node: FlatNode): void { + this.loadingNode = node; + if (node.parent != null) { + if (node.parent.isExpandable) { + if (node.id === 'collection') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; } - this.dataSource.loadCommunities(this.expandedNodes); - } - - getNextPage(node: FlatNode): void { - if (node.parent != null) { - if (node.parent.isExpandable) { - if (node.payload instanceof Collection) { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCollectionPage++; - } - if (node.payload instanceof Community) { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2:FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCommunityPage++; - } - } - this.dataSource.loadCommunities(this.expandedNodes); - } else { - this.communityListAdapter.getNextPageTopCommunities(); - this.dataSource.loadCommunities(this.expandedNodes); + if (node.id === 'community') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; } + } + this.dataSource.loadCommunities(this.expandedNodes); + } else { + this.communityListService.getNextPageTopCommunities(); + this.dataSource.loadCommunities(this.expandedNodes); } + } } diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts index 2e1eac9e9c..8902fdef5e 100644 --- a/src/app/core/data/collection-data.service.ts +++ b/src/app/core/data/collection-data.service.ts @@ -16,7 +16,7 @@ import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; import { Observable } from 'rxjs/internal/Observable'; -import { FindListOptions, GetRequest } from './request.models'; +import {FindListOptions, FindListRequest, GetRequest} from './request.models'; import { RemoteData } from './remote-data'; import { PaginatedList } from './paginated-list'; import { configureRequest } from '../shared/operators'; diff --git a/src/app/core/data/comcol-data.service.spec.ts b/src/app/core/data/comcol-data.service.spec.ts index ffefbba6f6..a7fcd205d4 100644 --- a/src/app/core/data/comcol-data.service.spec.ts +++ b/src/app/core/data/comcol-data.service.spec.ts @@ -13,7 +13,7 @@ import { RequestService } from './request.service'; import { NormalizedObject } from '../cache/models/normalized-object.model'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestEntry } from './request.reducer'; -import { of as observableOf } from 'rxjs'; +import {Observable, of as observableOf} from 'rxjs'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service'; @@ -45,6 +45,11 @@ class TestService extends ComColDataService { ) { super(); } + + protected getFindByParentHref(parentUUID: string): Observable { + // implementation in subclasses for communities/collections + return undefined; + } } /* tslint:enable:max-classes-per-file */ diff --git a/src/app/core/data/community-data.service.ts b/src/app/core/data/community-data.service.ts index d28c975122..57bf64678f 100644 --- a/src/app/core/data/community-data.service.ts +++ b/src/app/core/data/community-data.service.ts @@ -23,7 +23,6 @@ import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; export class CommunityDataService extends ComColDataService { protected linkPath = 'communities'; protected topLinkPath = 'communities/search/top'; - protected subcommunitiesLinkPath = 'communities/search/subCommunities'; protected cds = this; constructor( @@ -57,20 +56,6 @@ export class CommunityDataService extends ComColDataService { return this.rdbService.buildList(hrefObs) as Observable>>; } - findSubCommunitiesPerParentCommunity(parentCommunityUUID: string, options: FindListOptions = {}): Observable>> { - const hrefObs = this.getFindAllHref(options, this.subcommunitiesLinkPath + '?parent=' + parentCommunityUUID); - - hrefObs.pipe( - filter((href: string) => hasValue(href)), - take(1)) - .subscribe((href: string) => { - const request = new FindListRequest(this.requestService.generateRequestId(), href, options); - this.requestService.configure(request); - }); - - return this.rdbService.buildList(hrefObs) as Observable>>; - } - protected getFindByParentHref(parentUUID: string): Observable { return this.halService.getEndpoint(this.linkPath).pipe( switchMap((communityEndpointHref: string) => diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index 2518de9a7b..135547cdcc 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -128,9 +128,11 @@ export abstract class DataService { } findAll(options: FindListOptions = {}): Observable>> { - const hrefObs = this.getFindAllHref(options); + return this.findList(this.getFindAllHref(options), options); + } - hrefObs.pipe( + protected findList(href$, options: FindListOptions) { + href$.pipe( first((href: string) => hasValue(href))) .subscribe((href: string) => { const request = new FindListRequest(this.requestService.generateRequestId(), href, options); @@ -140,7 +142,7 @@ export abstract class DataService { this.requestService.configure(request); }); - return this.rdbService.buildList(hrefObs) as Observable>>; + return this.rdbService.buildList(href$) as Observable>>; } /** diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index 4c7c3cd030..b2ba10fb98 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit { } as TextMenuItemModel, index: 0 }, - // { - // id: 'browse_global_communities_and_collections', - // parentID: 'browse_global', - // active: false, - // visible: true, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.browse_global_communities_and_collections', - // link: '#' - // } as LinkMenuItemModel, - // }, + /* Communities & Collections tree */ + { + id: `browse_global_communities_and_collections`, + parentID: 'browse_global', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: `menu.section.browse_global_communities_and_collections`, + link: `/community-list` + } as LinkMenuItemModel + }, /* Statistics */ { From eaf0911e2e36712cdf5fcc13487368043b1298de Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 19 Nov 2019 18:11:03 +0100 Subject: [PATCH 27/46] 66391: Fixed the valse positive chevron indicating node is expandable --- .../community-list-service.ts | 502 +++++++++--------- .../community-list.component.html | 2 +- .../community-list.component.ts | 20 +- 3 files changed, 261 insertions(+), 263 deletions(-) diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts index 6da1bff4e5..3cbb76f79c 100644 --- a/src/app/community-list-page/community-list-service.ts +++ b/src/app/community-list-page/community-list-service.ts @@ -1,288 +1,288 @@ -import { Injectable } from '@angular/core'; -import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; -import { Observable, of as observableOf } from 'rxjs'; -import { CommunityDataService } from '../core/data/community-data.service'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; -import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators'; -import { Community } from '../core/shared/community.model'; -import { Collection } from '../core/shared/collection.model'; -import { hasValue, isNotEmpty } from '../shared/empty.util'; -import { RemoteData } from '../core/data/remote-data'; -import { PaginatedList } from '../core/data/paginated-list'; -import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; -import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; -import { CollectionDataService } from '../core/data/collection-data.service'; +import {Injectable} from '@angular/core'; +import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; +import {Observable, of as observableOf} from 'rxjs'; +import {CommunityDataService} from '../core/data/community-data.service'; +import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; +import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; +import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; +import {Community} from '../core/shared/community.model'; +import {Collection} from '../core/shared/collection.model'; +import {hasValue, isNotEmpty} from '../shared/empty.util'; +import {RemoteData} from '../core/data/remote-data'; +import {PaginatedList} from '../core/data/paginated-list'; +import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; +import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; +import {CollectionDataService} from '../core/data/collection-data.service'; export interface FlatNode { - isExpandable: boolean; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection | ShowMoreFlatNode; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; + isExpandable$: Observable; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } export class ShowMoreFlatNode { } export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); export const toFlatNode = ( - c: Community | Collection, - isExpandable: boolean, - level: number, - isExpanded: boolean, - parent?: FlatNode + c: Community | Collection, + isExpandable: Observable, + level: number, + isExpanded: boolean, + parent?: FlatNode ): FlatNode => ({ - isExpandable: isExpandable, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), + isExpandable$: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); export const showMoreFlatNode = ( - id: string, - level: number, - parent: FlatNode + id: string, + level: number, + parent: FlatNode ): FlatNode => ({ - isExpandable: false, - name: 'Show More Flatnode', - id: id, - level: level, - isExpanded: false, - parent: parent, - payload: new ShowMoreFlatNode(), - isShowMoreNode: true, + isExpandable$: observableOf(false), + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, }); // tslint:disable-next-line:max-classes-per-file @Injectable() export class CommunityListService { - // page-limited list of top-level communities - payloads$: Array>>; + // page-limited list of top-level communities + payloads$: Array>>; - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - maxSubCommunitiesPerPage: number; - maxCollectionsPerPage: number; + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList(); + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); - this.maxSubCommunitiesPerPage = 3; - this.maxCollectionsPerPage = 3; - } + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } - /** - * Increases the payload so it contains the next page of top level communities - */ - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } - /** - * Gets all top communities, limited by page, and transforms this in a list of flatnodes. - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); - }); - return combineAndFlatten(res); - }; - - /** - * Puts the initial top level communities in a list to be called upon - */ - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } - - /** - * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity - * @param listOfPaginatedCommunities Paginated list of communities to be transformed - * @param level Level the tree is currently at - * @param parent FlatNode of the parent of this list of communities - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) + /** + * Gets all top communities, limited by page, and transforms this in a list of flatnodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; - } + return combineAndFlatten(res); + }; - return combineAndFlatten(obsList); - } else { - return observableOf([]); - } - } - - /** - * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, - * followed by flatNodes of its possible subcommunities and collection - * It gets called recursively for each subcommunity to add its subcommunities and collections to the list - * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. - * @param community Community being transformed - * @param level Depth of the community in the list, subcommunities and collections go one level deeper - * @param parent Flatnode of the parent community - * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections - */ - public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const isExpandable = this.getIsExpandable(community); - - const communityFlatNode = toFlatNode(community, isExpandable, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms = []; - for (let i = 1; i <= currentCommunityPage; i++) { - const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { - elementsPerPage: this.maxSubCommunitiesPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); + map((results) => results.payload), + )]; + } - subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; - } + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) + }); + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } - obsList = [...obsList, combineAndFlatten(subcoms)]; + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } - const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; - let collections = []; - for (let i = 1; i <= currentCollectionPage; i++) { - const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { - elementsPerPage: this.maxCollectionsPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); + } + + const isExpandable$ = this.getIsExpandable(community); + + const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * Returns an observable that combines the result.payload.totalElements fo the observables that the + * respective services return when queried + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): Observable { + let hasSubcoms$: Observable; + let hasColls$: Observable; + hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1}) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + let hasChildren$: Observable; + hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( take(1), - map((rd: RemoteData>) => { - let nodes = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, false, level + 1, false, communityFlatNode)); - if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { - nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; - } - return nodes; - }), - ); - collections = [...collections, nextSetOfCollectionsPage]; - } - obsList = [...obsList, combineAndFlatten(collections)]; + map((result: [boolean]) => { + if (result[0] || result[1]) { + return true; + } else { + return false; + } + }) + ); + + return hasChildren$; } - return combineAndFlatten(obsList); - } - - /** - * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 - * If it does, it is expandable => returns true, false if not - * @param community Community being checked whether it is expandable (if it has subcommunities or collections) - */ - public getIsExpandable(community: Community): boolean { - let isExpandable = true; - let nrOfSubcoms; - let nrOfColl; - this.communityDataService.findByParent(community.uuid, { elementsPerPage: 0 }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - ) - .subscribe((result) => { - if (result.payload.totalElements === 0) { - nrOfSubcoms = result.payload.totalElements; - } - ; - }); - - this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 0 }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - ) - .subscribe((result) => { - if (result.payload.totalElements === 0) { - nrOfColl = result.payload.totalElements; - } - ; - }); - if (nrOfSubcoms === 0 && nrOfColl === 0) { - isExpandable = false; - } - return isExpandable; - } - } diff --git a/src/app/community-list-page/community-list/community-list.component.html b/src/app/community-list-page/community-list/community-list.component.html index fdec67d67c..9f8f511b12 100644 --- a/src/app/community-list-page/community-list/community-list.component.html +++ b/src/app/community-list-page/community-list/community-list.component.html @@ -28,7 +28,7 @@ diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index 622d03237e..c51c4adde8 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -14,7 +14,7 @@ export class CommunityListComponent implements OnInit { public loadingNode: FlatNode; treeControl = new FlatTreeControl( - (node) => node.level, (node) => node.isExpandable + (node) => node.level, (node) => true ); dataSource: CommunityListDatasource; @@ -29,7 +29,7 @@ export class CommunityListComponent implements OnInit { // whether or not this node has children (subcommunities or collections) hasChild(_: number, node: FlatNode) { - return node.isExpandable; + return node.isExpandable$; } // whether or not it is a show more node (contains no data, but is indication that there are more topcoms, subcoms or collections @@ -68,15 +68,13 @@ export class CommunityListComponent implements OnInit { getNextPage(node: FlatNode): void { this.loadingNode = node; if (node.parent != null) { - if (node.parent.isExpandable) { - if (node.id === 'collection') { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCollectionPage++; - } - if (node.id === 'community') { - const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); - parentNodeInExpandedNodes.currentCommunityPage++; - } + if (node.id === 'collection') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCollectionPage++; + } + if (node.id === 'community') { + const parentNodeInExpandedNodes = this.expandedNodes.find((node2: FlatNode) => node.parent.id === node2.id); + parentNodeInExpandedNodes.currentCommunityPage++; } this.dataSource.loadCommunities(this.expandedNodes); } else { From 9bd1933548f6e32164e4b524e5d6072a57e55ec5 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 17:16:21 +0100 Subject: [PATCH 28/46] 66391: expandedNodes (&loadingNode) is now retrieved/sent to the store at component initialisation/destruction so the state of the tree persists & documentation --- src/app/app.reducer.ts | 5 +- .../community-list-datasource.ts | 7 +- .../community-list-page.module.ts | 3 + .../community-list-page.routing.module.ts | 3 + .../community-list-service.ts | 549 ++++++++++-------- .../community-list.actions.ts | 35 ++ .../community-list.reducer.ts | 36 ++ .../community-list.component.ts | 24 +- 8 files changed, 406 insertions(+), 256 deletions(-) create mode 100644 src/app/community-list-page/community-list.actions.ts create mode 100644 src/app/community-list-page/community-list.reducer.ts diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts index bc84f961fb..f8c8b5df3d 100644 --- a/src/app/app.reducer.ts +++ b/src/app/app.reducer.ts @@ -1,5 +1,6 @@ import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store'; import * as fromRouter from '@ngrx/router-store'; +import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer'; import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer'; import { formReducer, FormState } from './shared/form/form.reducer'; import { @@ -43,6 +44,7 @@ export interface AppState { cssVariables: CSSVariablesState; menus: MenusState; objectSelection: ObjectSelectionListState; + communityList: CommunityListState; } export const appReducers: ActionReducerMap = { @@ -58,7 +60,8 @@ export const appReducers: ActionReducerMap = { truncatable: truncatableReducer, cssVariables: cssVariablesReducer, menus: menusReducer, - objectSelection: objectSelectionReducer + objectSelection: objectSelectionReducer, + communityList: CommunityListReducer, }; export const routerStateSelector = (state: AppState) => state.router; diff --git a/src/app/community-list-page/community-list-datasource.ts b/src/app/community-list-page/community-list-datasource.ts index da4bcc00cd..3a9d9f2077 100644 --- a/src/app/community-list-page/community-list-datasource.ts +++ b/src/app/community-list-page/community-list-datasource.ts @@ -3,6 +3,12 @@ import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections'; import { BehaviorSubject, Observable, } from 'rxjs'; import { finalize, take, } from 'rxjs/operators'; +/** + * DataSource object needed by a CDK Tree to render its nodes. + * The list of FlatNodes that this DataSource object represents gets created in the CommunityListService at + * the beginning (initial page-limited top communities) and re-calculated any time the tree state changes + * (a node gets expanded or page-limited result become larger by triggering a show more node) + */ export class CommunityListDatasource implements DataSource { private communityList$ = new BehaviorSubject([]); @@ -12,7 +18,6 @@ export class CommunityListDatasource implements DataSource { } connect(collectionViewer: CollectionViewer): Observable { - this.loadCommunities(null); return this.communityList$.asObservable(); } diff --git a/src/app/community-list-page/community-list-page.module.ts b/src/app/community-list-page/community-list-page.module.ts index 817e492bf7..2e3914fe03 100644 --- a/src/app/community-list-page/community-list-page.module.ts +++ b/src/app/community-list-page/community-list-page.module.ts @@ -6,6 +6,9 @@ import { CommunityListPageRoutingModule } from './community-list-page.routing.mo import { CommunityListComponent } from './community-list/community-list.component'; import { CdkTreeModule } from '@angular/cdk/tree'; +/** + * The page which houses a title and the community list, as described in community-list.component + */ @NgModule({ imports: [ CommonModule, diff --git a/src/app/community-list-page/community-list-page.routing.module.ts b/src/app/community-list-page/community-list-page.routing.module.ts index b226236af1..fe250cb96d 100644 --- a/src/app/community-list-page/community-list-page.routing.module.ts +++ b/src/app/community-list-page/community-list-page.routing.module.ts @@ -5,6 +5,9 @@ import { CdkTreeModule } from '@angular/cdk/tree'; import { CommunityListPageComponent } from './community-list-page.component'; import { CommunityListService } from './community-list-service'; +/** + * RouterModule to help navigate to the page with the community list tree + */ @NgModule({ imports: [ RouterModule.forChild([ diff --git a/src/app/community-list-page/community-list-service.ts b/src/app/community-list-page/community-list-service.ts index 3cbb76f79c..a25dbd2689 100644 --- a/src/app/community-list-page/community-list-service.ts +++ b/src/app/community-list-page/community-list-service.ts @@ -1,288 +1,335 @@ -import {Injectable} from '@angular/core'; -import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; -import {Observable, of as observableOf} from 'rxjs'; -import {CommunityDataService} from '../core/data/community-data.service'; -import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; -import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; -import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; -import {Community} from '../core/shared/community.model'; -import {Collection} from '../core/shared/collection.model'; -import {hasValue, isNotEmpty} from '../shared/empty.util'; -import {RemoteData} from '../core/data/remote-data'; -import {PaginatedList} from '../core/data/paginated-list'; -import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; -import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; -import {CollectionDataService} from '../core/data/collection-data.service'; +import { Injectable } from '@angular/core'; +import { createSelector, Store } from '@ngrx/store'; +import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest'; +import { Observable, of as observableOf } from 'rxjs'; +import { AppState } from '../app.reducer'; +import { CommunityDataService } from '../core/data/community-data.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model'; +import { catchError, filter, map, switchMap, take } from 'rxjs/operators'; +import { Community } from '../core/shared/community.model'; +import { Collection } from '../core/shared/collection.model'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { RemoteData } from '../core/data/remote-data'; +import { PaginatedList } from '../core/data/paginated-list'; +import { getCommunityPageRoute } from '../+community-page/community-page-routing.module'; +import { getCollectionPageRoute } from '../+collection-page/collection-page-routing.module'; +import { CollectionDataService } from '../core/data/collection-data.service'; +import { CommunityListSaveAction } from './community-list.actions'; +import { CommunityListState } from './community-list.reducer'; +/** + * Each node in the tree is represented by a flatNode which contains info about the node itself and its position and + * state in the tree. There are nodes representing communities, collections and show more links. + */ export interface FlatNode { - isExpandable$: Observable; - name: string; - id: string; - level: number; - isExpanded?: boolean; - parent?: FlatNode; - payload: Community | Collection | ShowMoreFlatNode; - isShowMoreNode: boolean; - route?: string; - currentCommunityPage?: number; - currentCollectionPage?: number; + isExpandable$: Observable; + name: string; + id: string; + level: number; + isExpanded?: boolean; + parent?: FlatNode; + payload: Community | Collection | ShowMoreFlatNode; + isShowMoreNode: boolean; + route?: string; + currentCommunityPage?: number; + currentCollectionPage?: number; } +/** + * The show more links in the community tree are also represented by a flatNode so we know where in + * the tree it should be rendered an who its parent is (needed for the action resulting in clicking this link) + */ export class ShowMoreFlatNode { } +// Helper method to combine an flatten an array of observables of flatNode arrays export const combineAndFlatten = (obsList: Array>): Observable => - observableCombineLatest(...obsList).pipe( - map((matrix: FlatNode[][]) => - matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) - ); + observableCombineLatest(...obsList).pipe( + map((matrix: FlatNode[][]) => + matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) + ); +/** + * Creates a flatNode from a community or collection + * @param c The community or collection this flatNode represents + * @param isExpandable Whether or not this node is expandable (true if it has children) + * @param level Level indicating how deep in the tree this node should be rendered + * @param isExpanded Whether or not this node already is expanded + * @param parent Parent of this node (flatNode representing its parent community) + */ export const toFlatNode = ( - c: Community | Collection, - isExpandable: Observable, - level: number, - isExpanded: boolean, - parent?: FlatNode + c: Community | Collection, + isExpandable: Observable, + level: number, + isExpanded: boolean, + parent?: FlatNode ): FlatNode => ({ - isExpandable$: isExpandable, - name: c.name, - id: c.id, - level: level, - isExpanded, - parent, - payload: c, - isShowMoreNode: false, - route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), + isExpandable$: isExpandable, + name: c.name, + id: c.id, + level: level, + isExpanded, + parent, + payload: c, + isShowMoreNode: false, + route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), }); +/** + * Creates a show More flatnode where only the level and parent are of importance + */ export const showMoreFlatNode = ( - id: string, - level: number, - parent: FlatNode + id: string, + level: number, + parent: FlatNode ): FlatNode => ({ - isExpandable$: observableOf(false), - name: 'Show More Flatnode', - id: id, - level: level, - isExpanded: false, - parent: parent, - payload: new ShowMoreFlatNode(), - isShowMoreNode: true, + isExpandable$: observableOf(false), + name: 'Show More Flatnode', + id: id, + level: level, + isExpanded: false, + parent: parent, + payload: new ShowMoreFlatNode(), + isShowMoreNode: true, }); +// Selectors the get the communityList data out of the store +const communityListStateSelector = (state: AppState) => state.communityList; +const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes); +const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode); + +/** + * Service class for the community list, responsible for the creating of the flat list used by communityList dataSource + * and connection to the store to retrieve and save the state of the community list + */ // tslint:disable-next-line:max-classes-per-file @Injectable() export class CommunityListService { - // page-limited list of top-level communities - payloads$: Array>>; + // page-limited list of top-level communities + payloads$: Array>>; - topCommunitiesConfig: PaginationComponentOptions; - topCommunitiesSortConfig: SortOptions; + topCommunitiesConfig: PaginationComponentOptions; + topCommunitiesSortConfig: SortOptions; - maxSubCommunitiesPerPage: number; - maxCollectionsPerPage: number; + maxSubCommunitiesPerPage: number; + maxCollectionsPerPage: number; - constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { - this.topCommunitiesConfig = new PaginationComponentOptions(); - this.topCommunitiesConfig.id = 'top-level-pagination'; - this.topCommunitiesConfig.pageSize = 10; - this.topCommunitiesConfig.currentPage = 1; - this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); - this.initTopCommunityList(); + constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService, + private store: Store) { + this.topCommunitiesConfig = new PaginationComponentOptions(); + this.topCommunitiesConfig.id = 'top-level-pagination'; + this.topCommunitiesConfig.pageSize = 10; + this.topCommunitiesConfig.currentPage = 1; + this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); + this.initTopCommunityList(); - this.maxSubCommunitiesPerPage = 3; - this.maxCollectionsPerPage = 3; - } + this.maxSubCommunitiesPerPage = 3; + this.maxCollectionsPerPage = 3; + } - /** - * Increases the payload so it contains the next page of top level communities - */ - getNextPageTopCommunities(): void { - this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; - this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; - } + saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void { + this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode)); + } - /** - * Gets all top communities, limited by page, and transforms this in a list of flatnodes. - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - loadCommunities(expandedNodes: FlatNode[]): Observable { - const res = this.payloads$.map((payload) => { - return payload.pipe( - take(1), - switchMap((result: PaginatedList) => { - return this.transformListOfCommunities(result, 0, null, expandedNodes); - }), - catchError(() => observableOf([])), - ); + getExpandedNodesFromStore(): Observable { + return this.store.select(expandedNodesSelector); + } + + getLoadingNodeFromStore(): Observable { + return this.store.select(loadingNodeSelector); + } + + /** + * Increases the payload so it contains the next page of top level communities + */ + getNextPageTopCommunities(): void { + this.topCommunitiesConfig.currentPage = this.topCommunitiesConfig.currentPage + 1; + this.payloads$ = [...this.payloads$, this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Gets all top communities, limited by page, and transforms this in a list of flatNodes. + * @param expandedNodes List of expanded nodes; if a node is not expanded its subCommunities and collections need + * not be added to the list + */ + loadCommunities(expandedNodes: FlatNode[]): Observable { + const res = this.payloads$.map((payload) => { + return payload.pipe( + take(1), + switchMap((result: PaginatedList) => { + return this.transformListOfCommunities(result, 0, null, expandedNodes); + }), + catchError(() => observableOf([])), + ); + }); + return combineAndFlatten(res); + }; + + /** + * Puts the initial top level communities in a list to be called upon + */ + private initTopCommunityList(): void { + this.payloads$ = [this.communityDataService.findTop({ + currentPage: this.topCommunitiesConfig.currentPage, + elementsPerPage: this.topCommunitiesConfig.pageSize, + sort: { + field: this.topCommunitiesSortConfig.field, + direction: this.topCommunitiesSortConfig.direction + } + }).pipe( + take(1), + map((results) => results.payload), + )]; + } + + /** + * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity + * @param listOfPaginatedCommunities Paginated list of communities to be transformed + * @param level Level the tree is currently at + * @param parent FlatNode of the parent of this list of communities + * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list + */ + public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, + level: number, + parent: FlatNode, + expandedNodes: FlatNode[]): Observable { + if (isNotEmpty(listOfPaginatedCommunities.page)) { + let currentPage = this.topCommunitiesConfig.currentPage; + if (isNotEmpty(parent)) { + currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; + } + const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); + let obsList = listOfPaginatedCommunities.page + .map((community: Community) => { + return this.transformCommunity(community, level, parent, expandedNodes) }); - return combineAndFlatten(res); - }; + if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { + obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; + } - /** - * Puts the initial top level communities in a list to be called upon - */ - private initTopCommunityList(): void { - this.payloads$ = [this.communityDataService.findTop({ - currentPage: this.topCommunitiesConfig.currentPage, - elementsPerPage: this.topCommunitiesConfig.pageSize, - sort: { - field: this.topCommunitiesSortConfig.field, - direction: this.topCommunitiesSortConfig.direction - } - }).pipe( - take(1), - map((results) => results.payload), - )]; + return combineAndFlatten(obsList); + } else { + return observableOf([]); + } + } + + /** + * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, + * followed by flatNodes of its possible subcommunities and collection + * It gets called recursively for each subcommunity to add its subcommunities and collections to the list + * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. + * @param community Community being transformed + * @param level Depth of the community in the list, subcommunities and collections go one level deeper + * @param parent Flatnode of the parent community + * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections + */ + public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { + let isExpanded = false; + if (isNotEmpty(expandedNodes)) { + isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); } - /** - * Transforms a list of communities to a list of FlatNodes according to the instructions detailed in transformCommunity - * @param listOfPaginatedCommunities Paginated list of communities to be transformed - * @param level Level the tree is currently at - * @param parent FlatNode of the parent of this list of communities - * @param expandedNodes List of expanded nodes; if a node is not expanded its subcommunities and collections need not be added to the list - */ - public transformListOfCommunities(listOfPaginatedCommunities: PaginatedList, - level: number, - parent: FlatNode, - expandedNodes: FlatNode[]): Observable { - if (isNotEmpty(listOfPaginatedCommunities.page)) { - let currentPage = this.topCommunitiesConfig.currentPage; - if (isNotEmpty(parent)) { - currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage; - } - const isNotAllCommunities = (listOfPaginatedCommunities.totalElements > (listOfPaginatedCommunities.elementsPerPage * currentPage)); - let obsList = listOfPaginatedCommunities.page - .map((community: Community) => { - return this.transformCommunity(community, level, parent, expandedNodes) - }); - if (isNotAllCommunities && listOfPaginatedCommunities.currentPage > currentPage) { - obsList = [...obsList, observableOf([showMoreFlatNode('community', level, parent)])]; - } + const isExpandable$ = this.getIsExpandable(community); - return combineAndFlatten(obsList); + const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); + + let obsList = [observableOf([communityFlatNode])]; + + if (isExpanded) { + const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; + let subcoms = []; + for (let i = 1; i <= currentCommunityPage; i++) { + const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { + elementsPerPage: this.maxSubCommunitiesPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + switchMap((rd: RemoteData>) => + this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) + ); + + subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; + } + + obsList = [...obsList, combineAndFlatten(subcoms)]; + + const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; + let collections = []; + for (let i = 1; i <= currentCollectionPage; i++) { + const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { + elementsPerPage: this.maxCollectionsPerPage, + currentPage: i + }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((rd: RemoteData>) => { + let nodes = rd.payload.page + .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); + if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { + nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; + } + return nodes; + }), + ); + collections = [...collections, nextSetOfCollectionsPage]; + } + obsList = [...obsList, combineAndFlatten(collections)]; + } + + return combineAndFlatten(obsList); + } + + /** + * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 + * Returns an observable that combines the result.payload.totalElements fo the observables that the + * respective services return when queried + * @param community Community being checked whether it is expandable (if it has subcommunities or collections) + */ + public getIsExpandable(community: Community): Observable { + let hasSubcoms$: Observable; + let hasColls$: Observable; + hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 }) + .pipe( + filter((rd: RemoteData>) => rd.hasSucceeded), + take(1), + map((results) => results.payload.totalElements > 0), + ); + + let hasChildren$: Observable; + hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( + take(1), + map((result: [boolean]) => { + if (result[0] || result[1]) { + return true; } else { - return observableOf([]); + return false; } - } + }) + ); - /** - * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself, - * followed by flatNodes of its possible subcommunities and collection - * It gets called recursively for each subcommunity to add its subcommunities and collections to the list - * Number of subcommunities and collections added, is dependant on the current page the parent is at for respectively subcommunities and collections. - * @param community Community being transformed - * @param level Depth of the community in the list, subcommunities and collections go one level deeper - * @param parent Flatnode of the parent community - * @param expandedNodes List of nodes which are expanded, if node is not expanded, it need not add its page-limited subcommunities or collections - */ - public transformCommunity(community: Community, level: number, parent: FlatNode, expandedNodes: FlatNode[]): Observable { - let isExpanded = false; - if (isNotEmpty(expandedNodes)) { - isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id))); - } - - const isExpandable$ = this.getIsExpandable(community); - - const communityFlatNode = toFlatNode(community, isExpandable$, level, isExpanded, parent); - - let obsList = [observableOf([communityFlatNode])]; - - if (isExpanded) { - const currentCommunityPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCommunityPage; - let subcoms = []; - for (let i = 1; i <= currentCommunityPage; i++) { - const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, { - elementsPerPage: this.maxSubCommunitiesPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - switchMap((rd: RemoteData>) => - this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes)) - ); - - subcoms = [...subcoms, nextSetOfSubcommunitiesPage]; - } - - obsList = [...obsList, combineAndFlatten(subcoms)]; - - const currentCollectionPage = expandedNodes.find((node: FlatNode) => node.id === community.id).currentCollectionPage; - let collections = []; - for (let i = 1; i <= currentCollectionPage; i++) { - const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, { - elementsPerPage: this.maxCollectionsPerPage, - currentPage: i - }) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((rd: RemoteData>) => { - let nodes = rd.payload.page - .map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode)); - if ((rd.payload.elementsPerPage * currentCollectionPage) < rd.payload.totalElements && rd.payload.currentPage > currentCollectionPage) { - nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)]; - } - return nodes; - }), - ); - collections = [...collections, nextSetOfCollectionsPage]; - } - obsList = [...obsList, combineAndFlatten(collections)]; - } - - return combineAndFlatten(obsList); - } - - /** - * Checks if a community has subcommunities or collections by querying the respective services with a pageSize = 0 - * Returns an observable that combines the result.payload.totalElements fo the observables that the - * respective services return when queried - * @param community Community being checked whether it is expandable (if it has subcommunities or collections) - */ - public getIsExpandable(community: Community): Observable { - let hasSubcoms$: Observable; - let hasColls$: Observable; - hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((results) => results.payload.totalElements > 0), - ); - - hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1}) - .pipe( - filter((rd: RemoteData>) => rd.hasSucceeded), - take(1), - map((results) => results.payload.totalElements > 0), - ); - - let hasChildren$: Observable; - hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe( - take(1), - map((result: [boolean]) => { - if (result[0] || result[1]) { - return true; - } else { - return false; - } - }) - ); - - return hasChildren$; - } + return hasChildren$; + } } diff --git a/src/app/community-list-page/community-list.actions.ts b/src/app/community-list-page/community-list.actions.ts new file mode 100644 index 0000000000..bfce6fba34 --- /dev/null +++ b/src/app/community-list-page/community-list.actions.ts @@ -0,0 +1,35 @@ +import { Action } from '@ngrx/store'; +import { type } from '../shared/ngrx/type'; +import { FlatNode } from './community-list-service'; + +/** + * All the action types of the community-list + */ + +export const CommunityListActionTypes = { + SAVE: type('dspace/community-list-page/SAVE') +}; + +/** + * Community list SAVE action + */ +export class CommunityListSaveAction implements Action { + + type = CommunityListActionTypes.SAVE; + + payload: { + expandedNodes: FlatNode[]; + loadingNode: FlatNode; + }; + + constructor(expandedNodes: FlatNode[], loadingNode: FlatNode) { + this.payload = { expandedNodes, loadingNode } + } +}; + +/** + * Export a type alias of all actions in this action group + * so that reducers can easily compose action types + */ + +export type CommunityListActions = CommunityListSaveAction; diff --git a/src/app/community-list-page/community-list.reducer.ts b/src/app/community-list-page/community-list.reducer.ts new file mode 100644 index 0000000000..ef53c7f94e --- /dev/null +++ b/src/app/community-list-page/community-list.reducer.ts @@ -0,0 +1,36 @@ +import { FlatNode } from './community-list-service'; +import { CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; + +/** + * States we wish to put in store concerning the community list + */ +export interface CommunityListState { + expandedNodes: FlatNode[]; + loadingNode: FlatNode; +} + +/** + * Initial starting state of the list of expandedNodes and the current loading node of the community list + */ +const initialState: CommunityListState = { + expandedNodes: [], + loadingNode: null, +}; + +/** + * Reducer to interact with store concerning objects for the community list + * @constructor + */ +export function CommunityListReducer(state = initialState, action: CommunityListSaveAction) { + switch (action.type) { + case CommunityListActionTypes.SAVE: { + return Object.assign({}, state, { + expandedNodes: (action as CommunityListSaveAction).payload.expandedNodes, + loadingNode: (action as CommunityListSaveAction).payload.loadingNode, + }) + } + default: { + return state; + } + } +} diff --git a/src/app/community-list-page/community-list/community-list.component.ts b/src/app/community-list-page/community-list/community-list.component.ts index c51c4adde8..ddcd49cd1c 100644 --- a/src/app/community-list-page/community-list/community-list.component.ts +++ b/src/app/community-list-page/community-list/community-list.component.ts @@ -1,14 +1,22 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { take } from 'rxjs/operators'; import { CommunityListService, FlatNode } from '../community-list-service'; import { CommunityListDatasource } from '../community-list-datasource'; import { FlatTreeControl } from '@angular/cdk/tree'; import { isEmpty } from '../../shared/empty.util'; +/** + * A tree-structured list of nodes representing the communities, their subCommunities and collections. + * Initially only the page-restricted top communities are shown. + * Each node can be expanded to show its children and all children are also page-limited. + * More pages of a page-limited result can be shown by pressing a show more node/link. + * Which nodes were expanded is kept in the store, so this persists across pages. + */ @Component({ selector: 'ds-community-list', templateUrl: './community-list.component.html', }) -export class CommunityListComponent implements OnInit { +export class CommunityListComponent implements OnInit, OnDestroy { private expandedNodes: FlatNode[] = []; public loadingNode: FlatNode; @@ -24,7 +32,17 @@ export class CommunityListComponent implements OnInit { ngOnInit() { this.dataSource = new CommunityListDatasource(this.communityListService); - this.dataSource.loadCommunities(null); + this.communityListService.getLoadingNodeFromStore().pipe(take(1)).subscribe((result) => { + this.loadingNode = result; + }); + this.communityListService.getExpandedNodesFromStore().pipe(take(1)).subscribe((result) => { + this.expandedNodes = [...result]; + this.dataSource.loadCommunities(this.expandedNodes); + }); + } + + ngOnDestroy(): void { + this.communityListService.saveCommunityListStateToStore(this.expandedNodes, this.loadingNode); } // whether or not this node has children (subcommunities or collections) From 69a99e9381fbb5e487d70ea3e8b35370b1d074c2 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 18:02:45 +0100 Subject: [PATCH 29/46] Fix after master rebase and tests updated for chevron rendering changes (AoT ok) --- .../related-items/related-items-component.ts | 6 ++-- .../community-list-service.spec.ts | 32 +++++++++++-------- .../community-list.component.spec.ts | 16 +++++----- src/app/core/data/bundle-data.service.ts | 4 +-- .../core/data/dso-redirect-data.service.ts | 4 +-- src/app/core/data/relationship.service.ts | 12 +++---- 6 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/app/+item-page/simple/related-items/related-items-component.ts b/src/app/+item-page/simple/related-items/related-items-component.ts index 9cbfd2c28d..ff5eb00c48 100644 --- a/src/app/+item-page/simple/related-items/related-items-component.ts +++ b/src/app/+item-page/simple/related-items/related-items-component.ts @@ -4,7 +4,7 @@ import { Observable } from 'rxjs/internal/Observable'; import { RemoteData } from '../../../core/data/remote-data'; import { PaginatedList } from '../../../core/data/paginated-list'; import { RelationshipService } from '../../../core/data/relationship.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; import { Subscription } from 'rxjs/internal/Subscription'; import { ViewMode } from '../../../core/shared/view-mode.model'; @@ -33,7 +33,7 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { * Default options to start a search request with * Optional input, should you wish a different page size (or other options) */ - @Input() options = Object.assign(new FindAllOptions(), { elementsPerPage: 5 }); + @Input() options = Object.assign(new FindListOptions(), { elementsPerPage: 5 }); /** * An i18n label to use as a title for the list (usually describes the relation) @@ -53,7 +53,7 @@ export class RelatedItemsComponent implements OnInit, OnDestroy { /** * Search options for displaying all elements in a list */ - allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 }); + allOptions = Object.assign(new FindListOptions(), { elementsPerPage: 9999 }); /** * The view-mode we're currently on diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts index c3cb24cc59..e55903a8c8 100644 --- a/src/app/community-list-page/community-list-service.spec.ts +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -1,4 +1,8 @@ -import { TestBed, inject, async, fakeAsync } from '@angular/core/testing'; +import { of as observableOf } from 'rxjs'; +import { TestBed, inject, async } from '@angular/core/testing'; +import { Store } from '@ngrx/store'; +import { AppState } from '../app.reducer'; +import { MockStore } from '../shared/testing/mock-store'; import { CommunityListService, FlatNode, toFlatNode } from './community-list-service'; import { CollectionDataService } from '../core/data/collection-data.service'; import { PaginatedList } from '../core/data/paginated-list'; @@ -10,10 +14,11 @@ import { } from '../shared/testing/utils'; import { Community } from '../core/shared/community.model'; import { Collection } from '../core/shared/collection.model'; -import { map, take } from 'rxjs/operators'; +import { take } from 'rxjs/operators'; import { FindListOptions } from '../core/data/request.models'; describe('CommunityListService', () => { + let store: MockStore; const standardElementsPerPage = 2; let collectionDataServiceStub: any; let communityDataServiceStub: any; @@ -179,10 +184,11 @@ describe('CommunityListService', () => { { provide: CollectionDataService, useValue: collectionDataServiceStub }, { provide: CommunityDataService, useValue: communityDataServiceStub },], }); - service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub); + store = TestBed.get(Store); + service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store); })); - afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub)); + afterAll(() => service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store)); it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => { expect(serviceIn).toBeTruthy(); @@ -242,7 +248,7 @@ describe('CommunityListService', () => { beforeEach(() => { const expandedNodes = []; mockListOfTopCommunitiesPage1.map((community: Community) => { - const communityFlatNode = toFlatNode(community, true, 0, true, null); + const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; expandedNodes.push(communityFlatNode); @@ -265,7 +271,7 @@ describe('CommunityListService', () => { }); describe('Just first top comm expanded, all page 1: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { beforeEach(() => { - const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], true, 0, true, null); + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[0], observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -284,7 +290,7 @@ describe('CommunityListService', () => { }); describe('Just second top comm expanded, collections at page 2: should return list containing flatnodes of the communities in the test list and all its possible page-limited children (subcommunities and collections)', () => { beforeEach(() => { - const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], true, 0, true, null); + const communityFlatNode = toFlatNode(mockListOfTopCommunitiesPage1[1], observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 2; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -336,7 +342,7 @@ describe('CommunityListService', () => { beforeEach(() => { const expandedNodes = []; listOfCommunities.map((community: Community) => { - const communityFlatNode = toFlatNode(community, true, 0, true, null); + const communityFlatNode = toFlatNode(community, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; expandedNodes.push(communityFlatNode); @@ -436,7 +442,7 @@ describe('CommunityListService', () => { }); let flatNodeList; beforeEach(() => { - const communityFlatNode = toFlatNode(communityWithSubcoms, true, 0, true, null); + const communityFlatNode = toFlatNode(communityWithSubcoms, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 1; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -475,7 +481,7 @@ describe('CommunityListService', () => { }); let flatNodeList; beforeEach(() => { - const communityFlatNode = toFlatNode(communityWithCollections, true, 0, true, null); + const communityFlatNode = toFlatNode(communityWithCollections, observableOf(true), 0, true, null); communityFlatNode.currentCollectionPage = 2; communityFlatNode.currentCommunityPage = 1; const expandedNodes = [communityFlatNode]; @@ -523,7 +529,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 1' }] } }); - expect(service.getIsExpandable(communityWithSubcoms)).toEqual(true); + expect(service.getIsExpandable(communityWithSubcoms)).toEqual(observableOf(true)); }); it('if community has collections', () => { const communityWithCollections = Object.assign(new Community(), { @@ -536,7 +542,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 2' }] } }); - expect(service.getIsExpandable(communityWithCollections)).toEqual(true); + expect(service.getIsExpandable(communityWithCollections)).toEqual(observableOf(true)); }); }); describe('should return false', () => { @@ -551,7 +557,7 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 3' }] } }); - expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(false); + expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(observableOf(false)); }); }); diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index c1c9285491..448c6d5b9f 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -17,7 +17,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils'; import { PaginatedList } from '../../core/data/paginated-list'; import { PageInfo } from '../../core/shared/page-info.model'; import { Collection } from '../../core/shared/collection.model'; -import { Observable, of as observableOf } from 'rxjs'; +import { of as observableOf } from 'rxjs'; import { By } from '@angular/platform-browser'; import { isEmpty, isNotEmpty } from '../../shared/empty.util'; @@ -89,7 +89,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), name: 'community1', - }), true, 0, false, null + }), observableOf(true), 0, false, null ), toFlatNode( Object.assign(new Community(), { @@ -98,7 +98,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [...mockCollectionsPage1, ...mockCollectionsPage2])), name: 'community2', - }), true, 0, false, null + }), observableOf(true), 0, false, null ), toFlatNode( Object.assign(new Community(), { @@ -107,7 +107,7 @@ describe('CommunityListComponent', () => { subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), name: 'community3', - }), false, 0, false, null + }), observableOf(false), 0, false, null ), ]; let communityListServiceStub; @@ -143,7 +143,7 @@ describe('CommunityListComponent', () => { flatnodes = []; const topFlatnodes = mockTopFlatnodesUnexpanded.slice(0, endPageIndex); topFlatnodes.map((topNode: FlatNode) => { - flatnodes = [...flatnodes, topNode] + flatnodes = [...flatnodes, topNode]; const expandedParent: FlatNode = expandedNodes.find((expandedNode: FlatNode) => expandedNode.id === topNode.id); if (isNotEmpty(expandedParent)) { const matchingTopComWithArrays = mockTopCommunitiesWithChildrenArrays.find((topcom) => topcom.id === topNode.id); @@ -151,12 +151,12 @@ describe('CommunityListComponent', () => { const possibleSubcoms: Community[] = matchingTopComWithArrays.subcommunities; let subComFlatnodes = []; possibleSubcoms.map((subcom: Community) => { - subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, false, topNode.level + 1, false, topNode)]; + subComFlatnodes = [...subComFlatnodes, toFlatNode(subcom, observableOf(false), topNode.level + 1, false, topNode)]; }); const possibleColls: Collection[] = matchingTopComWithArrays.collections; let collFlatnodes = []; possibleColls.map((coll: Collection) => { - collFlatnodes = [...collFlatnodes, toFlatNode(coll, false, topNode.level + 1, false, topNode)]; + collFlatnodes = [...collFlatnodes, toFlatNode(coll, observableOf(false), topNode.level + 1, false, topNode)]; }); if (isNotEmpty(subComFlatnodes)) { const endSubComIndex = this.subcommunityPageSize * expandedParent.currentCommunityPage; @@ -313,7 +313,7 @@ describe('CommunityListComponent', () => { expect(allNodes.find((foundEl) => { return (foundEl.nativeElement.textContent.trim() === coll.name); })).toBeTruthy(); - }) + }); expect(allNodes.length).toEqual(4); const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node')); expect(showMoreEl.length).toEqual(2); diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index 5962488c4f..a66f4124ee 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -11,7 +11,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DefaultChangeAnalyzer } from './default-change-analyzer.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; /** @@ -40,7 +40,7 @@ export class BundleDataService extends DataService { * @param {FindAllOptions} options * @returns {Observable} */ - getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { return this.halService.getEndpoint(this.linkPath); } } diff --git a/src/app/core/data/dso-redirect-data.service.ts b/src/app/core/data/dso-redirect-data.service.ts index 7e71f82bbf..f4999637b3 100644 --- a/src/app/core/data/dso-redirect-data.service.ts +++ b/src/app/core/data/dso-redirect-data.service.ts @@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv import { RequestService } from './request.service'; import { Store } from '@ngrx/store'; import { CoreState } from '../core.reducers'; -import { FindAllOptions, FindByIDRequest, IdentifierType } from './request.models'; +import { FindListOptions, FindByIDRequest, IdentifierType } from './request.models'; import { Observable } from 'rxjs'; import { RemoteData } from './remote-data'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; @@ -40,7 +40,7 @@ export class DsoRedirectDataService extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index c466bd15af..9cd71b2853 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -10,7 +10,7 @@ import { getRemoteDataPayload, getResponseFromEntry, getSucceededRemoteData } from '../shared/operators'; -import { DeleteRequest, FindAllOptions, RestRequest } from './request.models'; +import { DeleteRequest, FindListOptions, RestRequest } from './request.models'; import { Observable } from 'rxjs/internal/Observable'; import { RestResponse } from '../cache/response.models'; import { Item } from '../shared/item.model'; @@ -56,7 +56,7 @@ export class RelationshipService extends DataService { super(); } - getBrowseEndpoint(options: FindAllOptions = {}, linkPath: string = this.linkPath): Observable { + getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable { return this.halService.getEndpoint(linkPath); } @@ -228,7 +228,7 @@ export class RelationshipService extends DataService { * @param label * @param options */ - getRelatedItemsByLabel(item: Item, label: string, options?: FindAllOptions): Observable>> { + getRelatedItemsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { return this.getItemRelationshipsByLabel(item, label, options).pipe(paginatedRelationsToItems(item.uuid)); } @@ -239,10 +239,10 @@ export class RelationshipService extends DataService { * @param label * @param options */ - getItemRelationshipsByLabel(item: Item, label: string, options?: FindAllOptions): Observable>> { - let findAllOptions = new FindAllOptions(); + getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { + let findAllOptions = new FindListOptions(); if (options) { - findAllOptions = Object.assign(new FindAllOptions(), options); + findAllOptions = Object.assign(new FindListOptions(), options); } const searchParams = [ new SearchParam('label', label), new SearchParam('dso', item.id) ]; if (findAllOptions.searchParams) { From 5ff634a26facf8362f36e1afbde3f93f5180d627 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Tue, 26 Nov 2019 18:06:07 +0100 Subject: [PATCH 30/46] Last bit of doc --- src/app/community-list-page/community-list-page.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/community-list-page/community-list-page.component.ts b/src/app/community-list-page/community-list-page.component.ts index 9ba715c20a..5ab3cce5de 100644 --- a/src/app/community-list-page/community-list-page.component.ts +++ b/src/app/community-list-page/community-list-page.component.ts @@ -1,5 +1,9 @@ import { Component } from '@angular/core'; +/** + * Page with title and the community list tree, as described in community-list.component; + * navigated to with community-list.page.routing.module + */ @Component({ selector: 'ds-community-list-page', templateUrl: './community-list-page.component.html', From db7ebfb16e24dd01261adb1776da4d7c035c6020 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 27 Nov 2019 10:37:38 +0100 Subject: [PATCH 31/46] Existing community list test fixes --- .../community-list-service.spec.ts | 18 +++++++++++++----- .../community-list.component.spec.ts | 12 ++++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/app/community-list-page/community-list-service.spec.ts b/src/app/community-list-page/community-list-service.spec.ts index e55903a8c8..a150277d20 100644 --- a/src/app/community-list-page/community-list-service.spec.ts +++ b/src/app/community-list-page/community-list-service.spec.ts @@ -117,7 +117,7 @@ describe('CommunityListService', () => { beforeEach(async(() => { communityDataServiceStub = { findTop(options: FindListOptions = {}) { - const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2] + const allTopComs = [...mockListOfTopCommunitiesPage1, ...mockListOfTopCommunitiesPage2]; let currentPage = options.currentPage; const elementsPerPage = 3; if (currentPage === undefined) { @@ -182,7 +182,9 @@ describe('CommunityListService', () => { TestBed.configureTestingModule({ providers: [CommunityListService, { provide: CollectionDataService, useValue: collectionDataServiceStub }, - { provide: CommunityDataService, useValue: communityDataServiceStub },], + { provide: CommunityDataService, useValue: communityDataServiceStub }, + { provide: Store, useValue: MockStore }, + ], }); store = TestBed.get(Store); service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store); @@ -529,7 +531,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 1' }] } }); - expect(service.getIsExpandable(communityWithSubcoms)).toEqual(observableOf(true)); + service.getIsExpandable(communityWithSubcoms).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(true); + }); }); it('if community has collections', () => { const communityWithCollections = Object.assign(new Community(), { @@ -542,7 +546,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 2' }] } }); - expect(service.getIsExpandable(communityWithCollections)).toEqual(observableOf(true)); + service.getIsExpandable(communityWithCollections).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(true); + }); }); }); describe('should return false', () => { @@ -557,7 +563,9 @@ describe('CommunityListService', () => { 'dc.title': [{ language: 'en_US', value: 'Community 3' }] } }); - expect(service.getIsExpandable(communityWithNoSubcomsOrColls)).toEqual(observableOf(false)); + service.getIsExpandable(communityWithNoSubcomsOrColls).pipe(take(1)).subscribe((result) => { + expect(result).toEqual(false); + }); }); }); diff --git a/src/app/community-list-page/community-list/community-list.component.spec.ts b/src/app/community-list-page/community-list/community-list.component.spec.ts index 448c6d5b9f..c04aadda37 100644 --- a/src/app/community-list-page/community-list/community-list.component.spec.ts +++ b/src/app/community-list-page/community-list/community-list.component.spec.ts @@ -118,9 +118,21 @@ describe('CommunityListComponent', () => { topCurrentPage: 1, collectionPageSize: 2, subcommunityPageSize: 2, + expandedNodes: [], + loadingNode: null, getNextPageTopCommunities() { this.topCurrentPage++; }, + getLoadingNodeFromStore() { + return observableOf(this.loadingNode); + }, + getExpandedNodesFromStore() { + return observableOf(this.expandedNodes); + }, + saveCommunityListStateToStore(expandedNodes, loadingNode) { + this.expandedNodes = expandedNodes; + this.loadingNode = loadingNode; + }, loadCommunities(expandedNodes) { let flatnodes; let showMoreTopComNode = false; From 36e80e3624e4a09c5f2ca390dbda9060ad3e02a6 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:30:26 +0100 Subject: [PATCH 32/46] Add support for OTF fonts --- webpack/webpack.common.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 806c0c6f40..f70040c647 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -173,7 +173,7 @@ module.exports = (env) => { ] }, { - test: /\.(html|eot|ttf|svg|woff|woff2)$/, + test: /\.(html|eot|ttf|otf|svg|woff|woff2)$/, loader: 'raw-loader' } ] From c0962cb9669738525dc2d7be83056394069841ed Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:32:30 +0100 Subject: [PATCH 33/46] add supported fonts README --- resources/fonts/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/fonts/README.md diff --git a/resources/fonts/README.md b/resources/fonts/README.md new file mode 100644 index 0000000000..5f7e5ab777 --- /dev/null +++ b/resources/fonts/README.md @@ -0,0 +1,3 @@ +# Supported font formats + +DSpace 7 supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. \ No newline at end of file From afcf897bbe911b85800e4a7195fd12210a7c05b0 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 27 Nov 2019 13:37:37 +0100 Subject: [PATCH 34/46] Reducer test and e2e test for persisting tree state through store --- .../community-list-page.e2e-spec.ts | 28 ++++++++++++ .../community-list-page.po.ts | 27 +++++++++++ .../community-list.reducer.spec.ts | 45 +++++++++++++++++++ .../community-list.reducer.ts | 4 +- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 e2e/community-list-page/community-list-page.e2e-spec.ts create mode 100644 e2e/community-list-page/community-list-page.po.ts create mode 100644 src/app/community-list-page/community-list.reducer.spec.ts diff --git a/e2e/community-list-page/community-list-page.e2e-spec.ts b/e2e/community-list-page/community-list-page.e2e-spec.ts new file mode 100644 index 0000000000..84c7257726 --- /dev/null +++ b/e2e/community-list-page/community-list-page.e2e-spec.ts @@ -0,0 +1,28 @@ +import { CommunityListPageProtractor } from './community-list-page.po'; + +describe('protractor CommunityListPage', () => { + let page: CommunityListPageProtractor; + + beforeEach(() => { + page = new CommunityListPageProtractor(); + }); + + it('should contain page-limited top communities (at least 1 expandable community)', () => { + page.navigateToCommunityList(); + expect(page.anExpandableCommunityIsPresent()).toEqual(true) + }); + + describe('if expanded a node and navigating away, tree state gets saved', () => { + it('if navigating back, same node is expanded', () => { + page.navigateToCommunityList(); + const linkOfSecondNodeBeforeExpanding = page.getLinkOfSecondNode(); + page.toggleExpandFirstExpandableCommunity(); + const linkOfSecondNodeAfterExpanding = page.getLinkOfSecondNode(); + page.navigateToHome(); + page.navigateToCommunityList(); + expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeAfterExpanding); + page.toggleExpandFirstExpandableCommunity(); + expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeBeforeExpanding); + }); + }); +}); diff --git a/e2e/community-list-page/community-list-page.po.ts b/e2e/community-list-page/community-list-page.po.ts new file mode 100644 index 0000000000..ff8a2eeba7 --- /dev/null +++ b/e2e/community-list-page/community-list-page.po.ts @@ -0,0 +1,27 @@ +import { browser, by, element } from 'protractor'; + +export class CommunityListPageProtractor { + HOMEPAGE = '/home'; + COMMUNITY_LIST = '/community-list'; + + navigateToHome() { + return browser.get(this.HOMEPAGE); + } + + navigateToCommunityList() { + return browser.get(this.COMMUNITY_LIST); + } + + anExpandableCommunityIsPresent() { + return element(by.css('.expandable-node h5 a')).isPresent(); + } + + toggleExpandFirstExpandableCommunity() { + element(by.css('.expandable-node button')).click(); + } + + getLinkOfSecondNode() { + return element(by.css('.cdk-tree-node h5 a')).getAttribute('href'); + } + +} diff --git a/src/app/community-list-page/community-list.reducer.spec.ts b/src/app/community-list-page/community-list.reducer.spec.ts new file mode 100644 index 0000000000..63eaaccc03 --- /dev/null +++ b/src/app/community-list-page/community-list.reducer.spec.ts @@ -0,0 +1,45 @@ +import { of as observableOf } from 'rxjs/internal/observable/of'; +import { PaginatedList } from '../core/data/paginated-list'; +import { Community } from '../core/shared/community.model'; +import { PageInfo } from '../core/shared/page-info.model'; +import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils'; +import { toFlatNode } from './community-list-service'; +import { CommunityListSaveAction } from './community-list.actions'; +import { CommunityListReducer } from './community-list.reducer'; + +describe('communityListReducer', () => { + const mockSubcommunities1Page1 = [Object.assign(new Community(), { + id: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + uuid: 'ce64f48e-2c9b-411a-ac36-ee429c0e6a88', + name: 'subcommunity1', + })]; + const mockFlatNodeOfCommunity = toFlatNode( + Object.assign(new Community(), { + id: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + uuid: '7669c72a-3f2a-451f-a3b9-9210e7a4c02f', + subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockSubcommunities1Page1)), + collections: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), + name: 'community1', + }), observableOf(true), 0, false, null + ); + + it ('should set init state of the expandedNodes and loadingNode', () => { + const state = { + expandedNodes: [], + loadingNode: null, + }; + const action = new CommunityListSaveAction([], null); + const newState = CommunityListReducer(null, action); + expect(newState).toEqual(state); + }); + + it ('should save new state of the expandedNodes and loadingNode at a save action', () => { + const state = { + expandedNodes: [mockFlatNodeOfCommunity], + loadingNode: null, + }; + const action = new CommunityListSaveAction([mockFlatNodeOfCommunity], null); + const newState = CommunityListReducer(null, action); + expect(newState).toEqual(state); + }); +}); diff --git a/src/app/community-list-page/community-list.reducer.ts b/src/app/community-list-page/community-list.reducer.ts index ef53c7f94e..b455fc496a 100644 --- a/src/app/community-list-page/community-list.reducer.ts +++ b/src/app/community-list-page/community-list.reducer.ts @@ -1,5 +1,5 @@ import { FlatNode } from './community-list-service'; -import { CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; +import { CommunityListActions, CommunityListActionTypes, CommunityListSaveAction } from './community-list.actions'; /** * States we wish to put in store concerning the community list @@ -21,7 +21,7 @@ const initialState: CommunityListState = { * Reducer to interact with store concerning objects for the community list * @constructor */ -export function CommunityListReducer(state = initialState, action: CommunityListSaveAction) { +export function CommunityListReducer(state = initialState, action: CommunityListActions) { switch (action.type) { case CommunityListActionTypes.SAVE: { return Object.assign({}, state, { From d127e5f27cfa6e81b967180cbed291d90e9a09a7 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 13:48:06 +0100 Subject: [PATCH 35/46] remove version number --- resources/fonts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/fonts/README.md b/resources/fonts/README.md index 5f7e5ab777..e4817b8572 100644 --- a/resources/fonts/README.md +++ b/resources/fonts/README.md @@ -1,3 +1,3 @@ # Supported font formats -DSpace 7 supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. \ No newline at end of file +DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. From 8c2e63c2a7829c6a36cf14d0fdc288d96d6748cf Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 27 Nov 2019 14:55:18 +0100 Subject: [PATCH 36/46] fix issue where font urls in scss slow down the build --- package.json | 2 +- webpack/webpack.common.js | 26 ++++++------- yarn.lock | 78 ++++++++++++++------------------------- 3 files changed, 41 insertions(+), 65 deletions(-) diff --git a/package.json b/package.json index 3a54b941dd..ec6c025d76 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "rollup-plugin-node-globals": "1.2.1", "rollup-plugin-node-resolve": "^3.0.3", "rollup-plugin-terser": "^2.0.2", - "sass-loader": "7.1.0", + "sass-loader": "^7.1.0", "script-ext-html-webpack-plugin": "2.0.1", "source-map": "0.7.3", "source-map-loader": "0.2.4", diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index f70040c647..e63ae024ed 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -119,12 +119,6 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { @@ -132,6 +126,12 @@ module.exports = (env) => { includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, { loader: 'sass-resources-loader', options: { @@ -157,19 +157,19 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { sourceMap: true, includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } - } + }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, ] }, { diff --git a/yarn.lock b/yarn.lock index 69f4a072ae..5d7801aa4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2093,15 +2093,14 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -clone-deep@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" - integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: - for-own "^1.0.0" is-plain-object "^2.0.4" - kind-of "^6.0.0" - shallow-clone "^1.0.0" + kind-of "^6.0.2" + shallow-clone "^3.0.0" clone-stats@^0.0.1: version "0.0.1" @@ -4121,11 +4120,6 @@ font-awesome@4.7.0: resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4138,13 +4132,6 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6148,7 +6135,7 @@ loader-utils@^0.2.12, loader-utils@^0.2.15, loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= @@ -6157,7 +6144,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1 emojis-list "^2.0.0" json5 "^0.5.0" -loader-utils@^1.0.4: +loader-utils@^1.0.1, loader-utils@^1.0.4: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" dependencies: @@ -6371,11 +6358,6 @@ lodash.startswith@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" integrity sha1-xZjErc4YiiflMUVzHNxsDnF3YAw= -lodash.tail@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" - integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= - lodash.template@^3.0.0: version "3.6.2" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" @@ -6849,14 +6831,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -9654,17 +9628,16 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-loader@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" - integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w== +sass-loader@^7.1.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f" + integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA== dependencies: - clone-deep "^2.0.1" + clone-deep "^4.0.1" loader-utils "^1.0.1" - lodash.tail "^4.1.1" neo-async "^2.5.0" - pify "^3.0.0" - semver "^5.5.0" + pify "^4.0.1" + semver "^6.3.0" sass-resources-loader@^2.0.0: version "2.0.0" @@ -9769,7 +9742,7 @@ semver-intersect@^1.1.2: dependencies: semver "^5.0.0" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: +"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== @@ -9779,7 +9752,12 @@ semver@^5.0.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^6.1.1: +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.1, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -9910,14 +9888,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" - integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: - is-extendable "^0.1.1" - kind-of "^5.0.0" - mixin-object "^2.0.1" + kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" From 1b978124d1ef7e44c0e6ab27d3809ae965834611 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Wed, 27 Nov 2019 18:01:31 +0100 Subject: [PATCH 37/46] 66156: Removal of redundant fields, message update and aciveTab as observable --- resources/i18n/en.json5 | 4 ++-- .../tabbed-related-entities-search.component.html | 2 +- .../tabbed-related-entities-search.component.spec.ts | 11 +++++------ .../tabbed-related-entities-search.component.ts | 8 ++++++-- .../item-pages/org-unit/org-unit.component.html | 10 ---------- .../item-pages/org-unit/org-unit.component.html | 12 ------------ 6 files changed, 14 insertions(+), 33 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7e250490fc..4a47434f21 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -822,9 +822,9 @@ "item.page.relationships.isJournalOfPublication": "Publications", - "item.page.relationships.isOrgUnitOfPerson": "Persons", + "item.page.relationships.isOrgUnitOfPerson": "Authors", - "item.page.relationships.isOrgUnitOfProject": "Projects", + "item.page.relationships.isOrgUnitOfProject": "Research Projects", "item.page.subject": "Keywords", diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html index d3690c74be..f9642d2c01 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts index bc767476e1..2d2e682196 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.spec.ts @@ -8,6 +8,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { MockRouter } from '../../../../shared/mocks/mock-router'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { VarDirective } from '../../../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; describe('TabbedRelatedEntitiesSearchComponent', () => { let comp: TabbedRelatedEntitiesSearchComponent; @@ -34,11 +35,7 @@ describe('TabbedRelatedEntitiesSearchComponent', () => { { provide: ActivatedRoute, useValue: { - snapshot: { - queryParams: { - tab: mockRelationType - } - } + queryParams: observableOf({ tab: mockRelationType }) }, }, { provide: Router, useValue: router } @@ -56,7 +53,9 @@ describe('TabbedRelatedEntitiesSearchComponent', () => { }); it('should initialize the activeTab depending on the current query parameters', () => { - expect(comp.activeTab).toEqual(mockRelationType); + comp.activeTab$.subscribe((activeTab) => { + expect(activeTab).toEqual(mockRelationType); + }); }); describe('onTabChange', () => { diff --git a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts index 9fc4e7ec34..b01eb70720 100644 --- a/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts +++ b/src/app/+item-page/simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component.ts @@ -1,6 +1,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { Item } from '../../../../core/shared/item.model'; import { ActivatedRoute, Router } from '@angular/router'; +import { Observable } from 'rxjs/internal/Observable'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ds-tabbed-related-entities-search', @@ -42,7 +44,7 @@ export class TabbedRelatedEntitiesSearchComponent implements OnInit { /** * The active tab */ - activeTab: string; + activeTab$: Observable; constructor(private route: ActivatedRoute, private router: Router) { @@ -52,7 +54,9 @@ export class TabbedRelatedEntitiesSearchComponent implements OnInit { * If the url contains a "tab" query parameter, set this tab to be the active tab */ ngOnInit(): void { - this.activeTab = this.route.snapshot.queryParams.tab; + this.activeTab$ = this.route.queryParams.pipe( + map((params) => params.tab) + ); } /** diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index c9227338eb..1b23d567f5 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -24,16 +24,6 @@
- - - -
- - - - Date: Mon, 14 Oct 2019 09:53:22 +0200 Subject: [PATCH 38/46] Track page views and searches in DSpace with a custom Angulartics2 provider --- .../collection-page.component.html | 1 + .../collection-page.module.ts | 4 +- .../community-page.component.html | 1 + .../+community-page/community-page.module.ts | 6 +- .../+home-page/home-page-routing.module.ts | 17 +- src/app/+home-page/home-page.component.html | 3 + src/app/+home-page/home-page.component.ts | 21 +- src/app/+home-page/home-page.module.ts | 4 +- src/app/+home-page/home-page.resolver.ts | 25 +++ .../full/full-item-page.component.html | 1 + src/app/+item-page/item-page.module.ts | 4 +- .../simple/item-page.component.html | 1 + .../my-dspace-page.component.scss | 2 +- ...onfiguration-search-page.component.spec.ts | 2 +- .../configuration-search-page.component.ts | 8 +- .../filtered-search-page.component.spec.ts | 2 +- .../filtered-search-page.component.ts | 8 +- .../search-page-routing.module.ts | 3 +- .../+search-page/search-page.component.html | 52 +---- src/app/+search-page/search-page.component.ts | 180 +---------------- src/app/+search-page/search-page.module.ts | 10 +- .../search-configuration.service.ts | 4 +- .../search-service/search.service.ts | 60 ++++-- .../search-tracker.component.html | 1 + .../search-tracker.component.scss | 3 + .../+search-page/search-tracker.component.ts | 88 +++++++++ src/app/+search-page/search.component.html | 50 +++++ ...e.component.scss => search.component.scss} | 0 ...onent.spec.ts => search.component.spec.ts} | 12 +- src/app/+search-page/search.component.ts | 184 ++++++++++++++++++ src/app/app.component.spec.ts | 2 + src/app/app.component.ts | 4 + .../cache/models/normalized-site.model.ts | 13 ++ src/app/core/core.module.ts | 4 + .../data/search-response-parsing.service.ts | 4 + src/app/core/data/site-data.service.spec.ts | 104 ++++++++++ src/app/core/data/site-data.service.ts | 68 +++++++ src/app/core/shared/site.model.ts | 11 ++ .../shared/mocks/mock-angulartics.service.ts | 1 + src/app/shared/mocks/mock-request.service.ts | 3 +- .../angulartics/dspace-provider.spec.ts | 26 +++ .../statistics/angulartics/dspace-provider.ts | 38 ++++ .../dspace/view-tracker.component.html | 1 + .../dspace/view-tracker.component.scss | 3 + .../dspace/view-tracker.component.ts | 27 +++ src/app/statistics/statistics.module.ts | 36 ++++ src/app/statistics/statistics.service.spec.ts | 145 ++++++++++++++ src/app/statistics/statistics.service.ts | 93 +++++++++ src/app/statistics/track-request.model.ts | 4 + src/modules/app/browser-app.module.ts | 5 +- src/modules/app/server-app.module.ts | 7 + 51 files changed, 1080 insertions(+), 276 deletions(-) create mode 100644 src/app/+home-page/home-page.resolver.ts create mode 100644 src/app/+search-page/search-tracker.component.html create mode 100644 src/app/+search-page/search-tracker.component.scss create mode 100644 src/app/+search-page/search-tracker.component.ts create mode 100644 src/app/+search-page/search.component.html rename src/app/+search-page/{search-page.component.scss => search.component.scss} (100%) rename src/app/+search-page/{search-page.component.spec.ts => search.component.spec.ts} (95%) create mode 100644 src/app/+search-page/search.component.ts create mode 100644 src/app/core/cache/models/normalized-site.model.ts create mode 100644 src/app/core/data/site-data.service.spec.ts create mode 100644 src/app/core/data/site-data.service.ts create mode 100644 src/app/core/shared/site.model.ts create mode 100644 src/app/statistics/angulartics/dspace-provider.spec.ts create mode 100644 src/app/statistics/angulartics/dspace-provider.ts create mode 100644 src/app/statistics/angulartics/dspace/view-tracker.component.html create mode 100644 src/app/statistics/angulartics/dspace/view-tracker.component.scss create mode 100644 src/app/statistics/angulartics/dspace/view-tracker.component.ts create mode 100644 src/app/statistics/statistics.module.ts create mode 100644 src/app/statistics/statistics.service.spec.ts create mode 100644 src/app/statistics/statistics.service.ts create mode 100644 src/app/statistics/track-request.model.ts diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 436cd351a0..12d5c200fd 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -3,6 +3,7 @@ *ngVar="(collectionRD$ | async) as collectionRD">
+
+
diff --git a/src/app/+community-page/community-page.module.ts b/src/app/+community-page/community-page.module.ts index 6d63cadcc8..8b02471fc2 100644 --- a/src/app/+community-page/community-page.module.ts +++ b/src/app/+community-page/community-page.module.ts @@ -6,17 +6,19 @@ import { SharedModule } from '../shared/shared.module'; import { CommunityPageComponent } from './community-page.component'; import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component'; import { CommunityPageRoutingModule } from './community-page-routing.module'; -import {CommunityPageSubCommunityListComponent} from './sub-community-list/community-page-sub-community-list.component'; +import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { CommunityFormComponent } from './community-form/community-form.component'; import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component'; +import { StatisticsModule } from '../statistics/statistics.module'; @NgModule({ imports: [ CommonModule, SharedModule, - CommunityPageRoutingModule + CommunityPageRoutingModule, + StatisticsModule.forRoot() ], declarations: [ CommunityPageComponent, diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index d7dcc18f49..78da529906 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -2,12 +2,25 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { HomePageComponent } from './home-page.component'; +import { HomePageResolver } from './home-page.resolver'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } } + { + path: '', + component: HomePageComponent, + pathMatch: 'full', + data: {title: 'home.title'}, + resolve: { + site: HomePageResolver + } + } ]) + ], + providers: [ + HomePageResolver ] }) -export class HomePageRoutingModule { } +export class HomePageRoutingModule { +} diff --git a/src/app/+home-page/home-page.component.html b/src/app/+home-page/home-page.component.html index 39ba479033..5515df595b 100644 --- a/src/app/+home-page/home-page.component.html +++ b/src/app/+home-page/home-page.component.html @@ -1,5 +1,8 @@
+ + +
diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 902a0e820d..1b915ae683 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -1,9 +1,26 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { Site } from '../core/shared/site.model'; @Component({ selector: 'ds-home-page', styleUrls: ['./home-page.component.scss'], templateUrl: './home-page.component.html' }) -export class HomePageComponent { +export class HomePageComponent implements OnInit { + + site$:Observable; + + constructor( + private route:ActivatedRoute, + ) { + } + + ngOnInit():void { + this.site$ = this.route.data.pipe( + map((data) => data.site as Site), + ); + } } diff --git a/src/app/+home-page/home-page.module.ts b/src/app/+home-page/home-page.module.ts index c0c082b36c..51e978bbfe 100644 --- a/src/app/+home-page/home-page.module.ts +++ b/src/app/+home-page/home-page.module.ts @@ -6,12 +6,14 @@ import { HomePageRoutingModule } from './home-page-routing.module'; import { HomePageComponent } from './home-page.component'; import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component'; +import { StatisticsModule } from '../statistics/statistics.module'; @NgModule({ imports: [ CommonModule, SharedModule, - HomePageRoutingModule + HomePageRoutingModule, + StatisticsModule.forRoot() ], declarations: [ HomePageComponent, diff --git a/src/app/+home-page/home-page.resolver.ts b/src/app/+home-page/home-page.resolver.ts new file mode 100644 index 0000000000..1145d1d013 --- /dev/null +++ b/src/app/+home-page/home-page.resolver.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { SiteDataService } from '../core/data/site-data.service'; +import { Site } from '../core/shared/site.model'; +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; + +/** + * The class that resolve the Site object for a route + */ +@Injectable() +export class HomePageResolver implements Resolve { + constructor(private siteService:SiteDataService) { + } + + /** + * Method for resolving a site object + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable Emits the found Site object, or an error if something went wrong + */ + resolve(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable | Promise | Site { + return this.siteService.find().pipe(take(1)); + } +} diff --git a/src/app/+item-page/full/full-item-page.component.html b/src/app/+item-page/full/full-item-page.component.html index 7aec57da0c..c453df6bff 100644 --- a/src/app/+item-page/full/full-item-page.component.html +++ b/src/app/+item-page/full/full-item-page.component.html @@ -1,6 +1,7 @@
+
@@ -57,7 +57,7 @@ - +
From e77866a0d286002def39a2489368506c1b2a678d Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 14:19:31 +0100 Subject: [PATCH 43/46] Merged Dspace/dspace-angular/master and changed all new FindAllOptions references to FindListOptions --- src/app/core/data/bundle-data.service.ts | 2 +- src/app/core/data/relationship.service.ts | 12 ++++++------ src/app/core/data/site-data.service.spec.ts | 4 ++-- src/app/core/data/site-data.service.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/app/core/data/bundle-data.service.ts b/src/app/core/data/bundle-data.service.ts index a66f4124ee..280f727aad 100644 --- a/src/app/core/data/bundle-data.service.ts +++ b/src/app/core/data/bundle-data.service.ts @@ -37,7 +37,7 @@ export class BundleDataService extends DataService { /** * Get the endpoint for browsing bundles - * @param {FindAllOptions} options + * @param {FindListOptions} options * @returns {Observable} */ getBrowseEndpoint(options: FindListOptions = {}, linkPath?: string): Observable { diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts index 9cd71b2853..f102e2ca53 100644 --- a/src/app/core/data/relationship.service.ts +++ b/src/app/core/data/relationship.service.ts @@ -240,17 +240,17 @@ export class RelationshipService extends DataService { * @param options */ getItemRelationshipsByLabel(item: Item, label: string, options?: FindListOptions): Observable>> { - let findAllOptions = new FindListOptions(); + let findListOptions = new FindListOptions(); if (options) { - findAllOptions = Object.assign(new FindListOptions(), options); + findListOptions = Object.assign(new FindListOptions(), options); } const searchParams = [ new SearchParam('label', label), new SearchParam('dso', item.id) ]; - if (findAllOptions.searchParams) { - findAllOptions.searchParams = [...findAllOptions.searchParams, ...searchParams]; + if (findListOptions.searchParams) { + findListOptions.searchParams = [...findListOptions.searchParams, ...searchParams]; } else { - findAllOptions.searchParams = searchParams; + findListOptions.searchParams = searchParams; } - return this.searchBy('byLabel', findAllOptions); + return this.searchBy('byLabel', findListOptions); } /** diff --git a/src/app/core/data/site-data.service.spec.ts b/src/app/core/data/site-data.service.spec.ts index 189218b5cf..09fa7fb457 100644 --- a/src/app/core/data/site-data.service.spec.ts +++ b/src/app/core/data/site-data.service.spec.ts @@ -13,7 +13,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { of as observableOf } from 'rxjs'; import { RestResponse } from '../cache/response.models'; import { RequestEntry } from './request.reducer'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { TestScheduler } from 'rxjs/testing'; import { PaginatedList } from './paginated-list'; import { RemoteData } from './remote-data'; @@ -31,7 +31,7 @@ describe('SiteDataService', () => { }); const requestUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2'; - const options = Object.assign(new FindAllOptions(), {}); + const options = Object.assign(new FindListOptions(), {}); const getRequestEntry$ = (successful:boolean, statusCode:number, statusText:string) => { return observableOf({ diff --git a/src/app/core/data/site-data.service.ts b/src/app/core/data/site-data.service.ts index 4993d47226..7550594cda 100644 --- a/src/app/core/data/site-data.service.ts +++ b/src/app/core/data/site-data.service.ts @@ -10,7 +10,7 @@ import { HALEndpointService } from '../shared/hal-endpoint.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; import { HttpClient } from '@angular/common/http'; import { DSOChangeAnalyzer } from './dso-change-analyzer.service'; -import { FindAllOptions } from './request.models'; +import { FindListOptions } from './request.models'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { RemoteData } from './remote-data'; @@ -46,10 +46,10 @@ export class SiteDataService extends DataService { /** * Get the endpoint for browsing the site object - * @param {FindAllOptions} options + * @param {FindListOptions} options * @param {Observable} linkPath */ - getBrowseEndpoint(options:FindAllOptions, linkPath?:string):Observable { + getBrowseEndpoint(options:FindListOptions, linkPath?:string):Observable { return this.halService.getEndpoint(this.linkPath); } From 43680155670a7c7b96850dd9d665e81f05d97562 Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 15:03:07 +0100 Subject: [PATCH 44/46] e2e tests for community-list removed --- .../community-list-page.e2e-spec.ts | 28 ---------------- .../community-list-page.po.ts | 32 ------------------- 2 files changed, 60 deletions(-) delete mode 100644 e2e/community-list-page/community-list-page.e2e-spec.ts delete mode 100644 e2e/community-list-page/community-list-page.po.ts diff --git a/e2e/community-list-page/community-list-page.e2e-spec.ts b/e2e/community-list-page/community-list-page.e2e-spec.ts deleted file mode 100644 index 84c7257726..0000000000 --- a/e2e/community-list-page/community-list-page.e2e-spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CommunityListPageProtractor } from './community-list-page.po'; - -describe('protractor CommunityListPage', () => { - let page: CommunityListPageProtractor; - - beforeEach(() => { - page = new CommunityListPageProtractor(); - }); - - it('should contain page-limited top communities (at least 1 expandable community)', () => { - page.navigateToCommunityList(); - expect(page.anExpandableCommunityIsPresent()).toEqual(true) - }); - - describe('if expanded a node and navigating away, tree state gets saved', () => { - it('if navigating back, same node is expanded', () => { - page.navigateToCommunityList(); - const linkOfSecondNodeBeforeExpanding = page.getLinkOfSecondNode(); - page.toggleExpandFirstExpandableCommunity(); - const linkOfSecondNodeAfterExpanding = page.getLinkOfSecondNode(); - page.navigateToHome(); - page.navigateToCommunityList(); - expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeAfterExpanding); - page.toggleExpandFirstExpandableCommunity(); - expect(page.getLinkOfSecondNode()).toEqual(linkOfSecondNodeBeforeExpanding); - }); - }); -}); diff --git a/e2e/community-list-page/community-list-page.po.ts b/e2e/community-list-page/community-list-page.po.ts deleted file mode 100644 index 332aa5a80a..0000000000 --- a/e2e/community-list-page/community-list-page.po.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { browser, by, element, protractor } from 'protractor'; - -export class CommunityListPageProtractor { - HOMEPAGE = '/home'; - COMMUNITY_LIST = '/community-list'; - - navigateToHome() { - return browser.get(this.HOMEPAGE); - } - - navigateToCommunityList() { - browser.get(this.COMMUNITY_LIST); - const loading = element(by.css('.ds-loading')); - browser.wait(protractor.ExpectedConditions.invisibilityOf(loading), 10000); - return; - - } - - anExpandableCommunityIsPresent() { - console.log(element(by.css('body'))); - return element(by.css('.expandable-node h5 a')).isPresent(); - } - - toggleExpandFirstExpandableCommunity() { - element(by.css('.expandable-node button')).click(); - } - - getLinkOfSecondNode() { - return element(by.css('.cdk-tree-node h5 a')).getAttribute('href'); - } - -} From 7b31ad0345b7fcdd7e23cd3bef2d0677f79c087c Mon Sep 17 00:00:00 2001 From: Marie Verdonck Date: Wed, 4 Dec 2019 17:48:42 +0100 Subject: [PATCH 45/46] [DS-4400] Fix for minor edit metadata bug --- src/app/core/cache/models/normalized-item.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core/cache/models/normalized-item.model.ts b/src/app/core/cache/models/normalized-item.model.ts index c613c59a0c..9b7edf70c0 100644 --- a/src/app/core/cache/models/normalized-item.model.ts +++ b/src/app/core/cache/models/normalized-item.model.ts @@ -65,7 +65,7 @@ export class NormalizedItem extends NormalizedDSpaceObject { @relationship(Bundle, true) bundles: string[]; - @autoserialize + @deserialize @relationship(Relationship, true) relationships: string[]; From cdbb9b620360acb051c80eb983c0ee1f0de4b8f1 Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 6 Dec 2019 12:59:01 +0100 Subject: [PATCH 46/46] 67478: External source entries loading on search-option change --- .../dynamic-lookup-relation-external-source-tab.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts index efee24b8ef..a8332dd3a1 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts @@ -8,7 +8,7 @@ import { RemoteData } from '../../../../../../core/data/remote-data'; import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model'; import { ExternalSource } from '../../../../../../core/shared/external-source.model'; -import { switchMap } from 'rxjs/operators'; +import { startWith, switchMap } from 'rxjs/operators'; import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model'; import { Context } from '../../../../../../core/shared/context.model'; import { ListableObject } from '../../../../../object-collection/shared/listable-object.model'; @@ -62,7 +62,7 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit ngOnInit(): void { this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe( switchMap((searchOptions: PaginatedSearchOptions) => - this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions)) + this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined))) ) } }