mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
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:
@@ -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;
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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([
|
||||||
|
@@ -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$;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
35
src/app/community-list-page/community-list.actions.ts
Normal file
35
src/app/community-list-page/community-list.actions.ts
Normal 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;
|
36
src/app/community-list-page/community-list.reducer.ts
Normal file
36
src/app/community-list-page/community-list.reducer.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user