66391: expandedNodes (&loadingNode) is now retrieved/sent to the store

at component initialisation/destruction so the state of the tree persists
& documentation
This commit is contained in:
Marie Verdonck
2019-11-26 17:16:21 +01:00
parent eaf0911e2e
commit 9bd1933548
8 changed files with 406 additions and 256 deletions

View File

@@ -1,5 +1,6 @@
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store'; import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
import * as fromRouter from '@ngrx/router-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 { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
import { formReducer, FormState } from './shared/form/form.reducer'; import { formReducer, FormState } from './shared/form/form.reducer';
import { import {
@@ -43,6 +44,7 @@ export interface AppState {
cssVariables: CSSVariablesState; cssVariables: CSSVariablesState;
menus: MenusState; menus: MenusState;
objectSelection: ObjectSelectionListState; objectSelection: ObjectSelectionListState;
communityList: CommunityListState;
} }
export const appReducers: ActionReducerMap<AppState> = { export const appReducers: ActionReducerMap<AppState> = {
@@ -58,7 +60,8 @@ export const appReducers: ActionReducerMap<AppState> = {
truncatable: truncatableReducer, truncatable: truncatableReducer,
cssVariables: cssVariablesReducer, cssVariables: cssVariablesReducer,
menus: menusReducer, menus: menusReducer,
objectSelection: objectSelectionReducer objectSelection: objectSelectionReducer,
communityList: CommunityListReducer,
}; };
export const routerStateSelector = (state: AppState) => state.router; export const routerStateSelector = (state: AppState) => state.router;

View File

@@ -3,6 +3,12 @@ import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
import { BehaviorSubject, Observable, } from 'rxjs'; import { BehaviorSubject, Observable, } from 'rxjs';
import { finalize, take, } from 'rxjs/operators'; 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<FlatNode> { export class CommunityListDatasource implements DataSource<FlatNode> {
private communityList$ = new BehaviorSubject<FlatNode[]>([]); private communityList$ = new BehaviorSubject<FlatNode[]>([]);
@@ -12,7 +18,6 @@ export class CommunityListDatasource implements DataSource<FlatNode> {
} }
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> { connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
this.loadCommunities(null);
return this.communityList$.asObservable(); return this.communityList$.asObservable();
} }

View File

@@ -6,6 +6,9 @@ import { CommunityListPageRoutingModule } from './community-list-page.routing.mo
import { CommunityListComponent } from './community-list/community-list.component'; import { CommunityListComponent } from './community-list/community-list.component';
import { CdkTreeModule } from '@angular/cdk/tree'; import { CdkTreeModule } from '@angular/cdk/tree';
/**
* The page which houses a title and the community list, as described in community-list.component
*/
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -5,6 +5,9 @@ import { CdkTreeModule } from '@angular/cdk/tree';
import { CommunityListPageComponent } from './community-list-page.component'; import { CommunityListPageComponent } from './community-list-page.component';
import { CommunityListService } from './community-list-service'; import { CommunityListService } from './community-list-service';
/**
* RouterModule to help navigate to the page with the community list tree
*/
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([

View File

@@ -1,288 +1,335 @@
import {Injectable} from '@angular/core'; import { Injectable } from '@angular/core';
import {combineLatest as observableCombineLatest} from 'rxjs/internal/observable/combineLatest'; import { createSelector, Store } from '@ngrx/store';
import {Observable, of as observableOf} from 'rxjs'; import { combineLatest as observableCombineLatest } from 'rxjs/internal/observable/combineLatest';
import {CommunityDataService} from '../core/data/community-data.service'; import { Observable, of as observableOf } from 'rxjs';
import {PaginationComponentOptions} from '../shared/pagination/pagination-component-options.model'; import { AppState } from '../app.reducer';
import {SortDirection, SortOptions} from '../core/cache/models/sort-options.model'; import { CommunityDataService } from '../core/data/community-data.service';
import {catchError, filter, map, switchMap, take} from 'rxjs/operators'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import {Community} from '../core/shared/community.model'; import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
import {Collection} from '../core/shared/collection.model'; import { catchError, filter, map, switchMap, take } from 'rxjs/operators';
import {hasValue, isNotEmpty} from '../shared/empty.util'; import { Community } from '../core/shared/community.model';
import {RemoteData} from '../core/data/remote-data'; import { Collection } from '../core/shared/collection.model';
import {PaginatedList} from '../core/data/paginated-list'; import { hasValue, isNotEmpty } from '../shared/empty.util';
import {getCommunityPageRoute} from '../+community-page/community-page-routing.module'; import { RemoteData } from '../core/data/remote-data';
import {getCollectionPageRoute} from '../+collection-page/collection-page-routing.module'; import { PaginatedList } from '../core/data/paginated-list';
import {CollectionDataService} from '../core/data/collection-data.service'; 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 { export interface FlatNode {
isExpandable$: Observable<boolean>; isExpandable$: Observable<boolean>;
name: string; name: string;
id: string; id: string;
level: number; level: number;
isExpanded?: boolean; isExpanded?: boolean;
parent?: FlatNode; parent?: FlatNode;
payload: Community | Collection | ShowMoreFlatNode; payload: Community | Collection | ShowMoreFlatNode;
isShowMoreNode: boolean; isShowMoreNode: boolean;
route?: string; route?: string;
currentCommunityPage?: number; currentCommunityPage?: number;
currentCollectionPage?: 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 { export class ShowMoreFlatNode {
} }
// Helper method to combine an flatten an array of observables of flatNode arrays
export const combineAndFlatten = (obsList: Array<Observable<FlatNode[]>>): Observable<FlatNode[]> => export const combineAndFlatten = (obsList: Array<Observable<FlatNode[]>>): Observable<FlatNode[]> =>
observableCombineLatest(...obsList).pipe( observableCombineLatest(...obsList).pipe(
map((matrix: FlatNode[][]) => map((matrix: FlatNode[][]) =>
matrix.reduce((combinedList, currentList: FlatNode[]) => [...combinedList, ...currentList])) 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 = ( export const toFlatNode = (
c: Community | Collection, c: Community | Collection,
isExpandable: Observable<boolean>, isExpandable: Observable<boolean>,
level: number, level: number,
isExpanded: boolean, isExpanded: boolean,
parent?: FlatNode parent?: FlatNode
): FlatNode => ({ ): FlatNode => ({
isExpandable$: isExpandable, isExpandable$: isExpandable,
name: c.name, name: c.name,
id: c.id, id: c.id,
level: level, level: level,
isExpanded, isExpanded,
parent, parent,
payload: c, payload: c,
isShowMoreNode: false, isShowMoreNode: false,
route: c instanceof Community ? getCommunityPageRoute(c.id) : getCollectionPageRoute(c.id), 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 = ( export const showMoreFlatNode = (
id: string, id: string,
level: number, level: number,
parent: FlatNode parent: FlatNode
): FlatNode => ({ ): FlatNode => ({
isExpandable$: observableOf(false), isExpandable$: observableOf(false),
name: 'Show More Flatnode', name: 'Show More Flatnode',
id: id, id: id,
level: level, level: level,
isExpanded: false, isExpanded: false,
parent: parent, parent: parent,
payload: new ShowMoreFlatNode(), payload: new ShowMoreFlatNode(),
isShowMoreNode: true, 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 // tslint:disable-next-line:max-classes-per-file
@Injectable() @Injectable()
export class CommunityListService { export class CommunityListService {
// page-limited list of top-level communities // page-limited list of top-level communities
payloads$: Array<Observable<PaginatedList<Community>>>; payloads$: Array<Observable<PaginatedList<Community>>>;
topCommunitiesConfig: PaginationComponentOptions; topCommunitiesConfig: PaginationComponentOptions;
topCommunitiesSortConfig: SortOptions; topCommunitiesSortConfig: SortOptions;
maxSubCommunitiesPerPage: number; maxSubCommunitiesPerPage: number;
maxCollectionsPerPage: number; maxCollectionsPerPage: number;
constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService) { constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService,
this.topCommunitiesConfig = new PaginationComponentOptions(); private store: Store<any>) {
this.topCommunitiesConfig.id = 'top-level-pagination'; this.topCommunitiesConfig = new PaginationComponentOptions();
this.topCommunitiesConfig.pageSize = 10; this.topCommunitiesConfig.id = 'top-level-pagination';
this.topCommunitiesConfig.currentPage = 1; this.topCommunitiesConfig.pageSize = 10;
this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC); this.topCommunitiesConfig.currentPage = 1;
this.initTopCommunityList(); this.topCommunitiesSortConfig = new SortOptions('dc.title', SortDirection.ASC);
this.initTopCommunityList();
this.maxSubCommunitiesPerPage = 3; this.maxSubCommunitiesPerPage = 3;
this.maxCollectionsPerPage = 3; this.maxCollectionsPerPage = 3;
} }
/** saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void {
* Increases the payload so it contains the next page of top level communities this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode));
*/ }
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),
)];
}
/** getExpandedNodesFromStore(): Observable<FlatNode[]> {
* Gets all top communities, limited by page, and transforms this in a list of flatnodes. return this.store.select(expandedNodesSelector);
* @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<FlatNode[]> { getLoadingNodeFromStore(): Observable<FlatNode> {
const res = this.payloads$.map((payload) => { return this.store.select(loadingNodeSelector);
return payload.pipe( }
take(1),
switchMap((result: PaginatedList<Community>) => { /**
return this.transformListOfCommunities(result, 0, null, expandedNodes); * Increases the payload so it contains the next page of top level communities
}), */
catchError(() => observableOf([])), 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<FlatNode[]> {
const res = this.payloads$.map((payload) => {
return payload.pipe(
take(1),
switchMap((result: PaginatedList<Community>) => {
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<Community>,
level: number,
parent: FlatNode,
expandedNodes: FlatNode[]): Observable<FlatNode[]> {
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)])];
}
/** return combineAndFlatten(obsList);
* Puts the initial top level communities in a list to be called upon } else {
*/ return observableOf([]);
private initTopCommunityList(): void { }
this.payloads$ = [this.communityDataService.findTop({ }
currentPage: this.topCommunitiesConfig.currentPage,
elementsPerPage: this.topCommunitiesConfig.pageSize, /**
sort: { * Transforms a community in a list of FlatNodes containing firstly a flatnode of the community itself,
field: this.topCommunitiesSortConfig.field, * followed by flatNodes of its possible subcommunities and collection
direction: this.topCommunitiesSortConfig.direction * 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.
}).pipe( * @param community Community being transformed
take(1), * @param level Depth of the community in the list, subcommunities and collections go one level deeper
map((results) => results.payload), * @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<FlatNode[]> {
let isExpanded = false;
if (isNotEmpty(expandedNodes)) {
isExpanded = hasValue(expandedNodes.find((node) => (node.id === community.id)));
} }
/** const isExpandable$ = this.getIsExpandable(community);
* 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<Community>,
level: number,
parent: FlatNode,
expandedNodes: FlatNode[]): Observable<FlatNode[]> {
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); 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<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
switchMap((rd: RemoteData<PaginatedList<Community>>) =>
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<PaginatedList<Collection>>) => rd.hasSucceeded),
take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => {
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<boolean> {
let hasSubcoms$: Observable<boolean>;
let hasColls$: Observable<boolean>;
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, { elementsPerPage: 1 })
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
let hasChildren$: Observable<boolean>;
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
take(1),
map((result: [boolean]) => {
if (result[0] || result[1]) {
return true;
} else { } else {
return observableOf([]); return false;
} }
} })
);
/** return hasChildren$;
* 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<FlatNode[]> {
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<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
switchMap((rd: RemoteData<PaginatedList<Community>>) =>
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<PaginatedList<Collection>>) => rd.hasSucceeded),
take(1),
map((rd: RemoteData<PaginatedList<Collection>>) => {
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<boolean> {
let hasSubcoms$: Observable<boolean>;
let hasColls$: Observable<boolean>;
hasSubcoms$ = this.communityDataService.findByParent(community.uuid, {elementsPerPage: 1})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
hasColls$ = this.collectionDataService.findByParent(community.uuid, {elementsPerPage: 1})
.pipe(
filter((rd: RemoteData<PaginatedList<Community>>) => rd.hasSucceeded),
take(1),
map((results) => results.payload.totalElements > 0),
);
let hasChildren$: Observable<boolean>;
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
take(1),
map((result: [boolean]) => {
if (result[0] || result[1]) {
return true;
} else {
return false;
}
})
);
return hasChildren$;
}
} }

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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 { CommunityListService, FlatNode } from '../community-list-service';
import { CommunityListDatasource } from '../community-list-datasource'; import { CommunityListDatasource } from '../community-list-datasource';
import { FlatTreeControl } from '@angular/cdk/tree'; import { FlatTreeControl } from '@angular/cdk/tree';
import { isEmpty } from '../../shared/empty.util'; 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({ @Component({
selector: 'ds-community-list', selector: 'ds-community-list',
templateUrl: './community-list.component.html', templateUrl: './community-list.component.html',
}) })
export class CommunityListComponent implements OnInit { export class CommunityListComponent implements OnInit, OnDestroy {
private expandedNodes: FlatNode[] = []; private expandedNodes: FlatNode[] = [];
public loadingNode: FlatNode; public loadingNode: FlatNode;
@@ -24,7 +32,17 @@ export class CommunityListComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.dataSource = new CommunityListDatasource(this.communityListService); 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) // whether or not this node has children (subcommunities or collections)