mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-17 15:03:07 +00:00

# Conflicts: # src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.spec.ts # src/app/+admin/admin-access-control/epeople-registry/epeople-registry.component.ts # src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts # src/app/+admin/admin-access-control/group-registry/group-form/group-form.component.spec.ts # src/app/+admin/admin-access-control/group-registry/group-form/members-list/members-list.component.spec.ts # src/app/+admin/admin-access-control/group-registry/group-form/subgroup-list/subgroups-list.component.spec.ts # src/app/+admin/admin-access-control/group-registry/groups-registry.component.ts # src/app/+admin/admin-import-metadata-page/metadata-import-page.component.ts # src/app/+admin/admin-registries/metadata-registry/metadata-registry.component.ts # src/app/+admin/admin-registries/metadata-schema/metadata-schema.component.ts # src/app/+admin/admin-search-page/admin-search-results/admin-search-result-grid-element/item-search-result/item-admin-search-result-grid-element.component.spec.ts # src/app/+admin/admin-search-page/admin-search-results/admin-search-result-list-element/item-search-result/item-admin-search-result-list-element.component.spec.ts # src/app/+bitstream-page/bitstream-page.resolver.ts # src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.spec.ts # src/app/+bitstream-page/edit-bitstream-page/edit-bitstream-page.component.ts # src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts # src/app/+collection-page/collection-page.component.ts # src/app/+collection-page/edit-collection-page/collection-roles/collection-roles.component.ts # src/app/+collection-page/edit-item-template-page/item-template-page.resolver.spec.ts # src/app/+collection-page/edit-item-template-page/item-template-page.resolver.ts # src/app/+item-page/edit-item-page/item-authorizations/item-authorizations.component.ts # src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.spec.ts # src/app/+item-page/edit-item-page/item-bitstreams/item-bitstreams.component.ts # src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts # src/app/+item-page/edit-item-page/item-private/item-private.component.ts # src/app/+item-page/edit-item-page/item-public/item-public.component.ts # src/app/+item-page/edit-item-page/item-reinstate/item-reinstate.component.ts # src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.ts # src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.ts # src/app/+item-page/edit-item-page/item-withdraw/item-withdraw.component.ts # src/app/+item-page/simple/item-types/shared/item-relationships-utils.ts # src/app/+my-dspace-page/collection-selector/collection-selector.component.spec.ts # src/app/+search-page/search-tracker.component.ts # src/app/app-routing.module.ts # src/app/community-list-page/community-list-service.ts # src/app/community-list-page/community-list.reducer.spec.ts # src/app/core/auth/auth-response-parsing.service.spec.ts # src/app/core/auth/auth.interceptor.spec.ts # src/app/core/browse/browse.service.spec.ts # src/app/core/browse/browse.service.ts # src/app/core/cache/builders/link.service.spec.ts # src/app/core/cache/builders/remote-data-build.service.ts # src/app/core/cache/object-cache.service.spec.ts # src/app/core/cache/response.models.ts # src/app/core/data/base-response-parsing.service.ts # src/app/core/data/bitstream-data.service.ts # src/app/core/data/bitstream-format-data.service.ts # src/app/core/data/bundle-data.service.spec.ts # src/app/core/data/bundle-data.service.ts # src/app/core/data/change-analyzer.ts # src/app/core/data/collection-data.service.spec.ts # src/app/core/data/collection-data.service.ts # src/app/core/data/comcol-data.service.spec.ts # src/app/core/data/comcol-data.service.ts # src/app/core/data/data.service.ts # src/app/core/data/entity-type-data.service.ts # src/app/core/data/entity-type.service.ts # src/app/core/data/feature-authorization/authorization-data.service.ts # src/app/core/data/feature-authorization/feature-data.service.ts # src/app/core/data/item-template-data.service.ts # src/app/core/data/license-data.service.ts # src/app/core/data/metadata-field-data.service.ts # src/app/core/data/processes/process-data.service.ts # src/app/core/data/processes/script-data.service.ts # src/app/core/data/relationship.service.spec.ts # src/app/core/data/relationship.service.ts # src/app/core/data/remote-data.ts # src/app/core/data/request.actions.ts # src/app/core/data/request.effects.ts # src/app/core/data/request.reducer.spec.ts # src/app/core/data/request.reducer.ts # src/app/core/data/request.service.spec.ts # src/app/core/data/request.service.ts # src/app/core/dspace-rest/dspace-rest.service.spec.ts # src/app/core/eperson/eperson-data.service.spec.ts # src/app/core/eperson/eperson-data.service.ts # src/app/core/eperson/group-data.service.spec.ts # src/app/core/eperson/group-data.service.ts # src/app/core/forward-client-ip/forward-client-ip.interceptor.spec.ts # src/app/core/index/index.reducer.ts # src/app/core/json-patch/json-patch-operations.service.spec.ts # src/app/core/json-patch/json-patch-operations.service.ts # src/app/core/locale/locale.interceptor.spec.ts # src/app/core/registry/registry.service.ts # src/app/core/resource-policy/resource-policy.service.ts # src/app/core/shared/operators.ts # src/app/core/shared/search/search.service.ts # src/app/core/submission/submission-object-data.service.ts # src/app/core/submission/submission-rest.service.ts # src/app/core/submission/vocabularies/vocabulary-entries-response-parsing.service.spec.ts # src/app/core/submission/vocabularies/vocabulary.service.spec.ts # src/app/core/submission/vocabularies/vocabulary.service.ts # src/app/core/submission/workflowitem-data.service.ts # src/app/core/tasks/tasks.service.ts # src/app/curation-form/curation-form.component.ts # src/app/process-page/process-page.resolver.ts # src/app/shared/collection-dropdown/collection-dropdown.component.spec.ts # src/app/shared/collection-dropdown/collection-dropdown.component.ts # src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts # src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts # src/app/shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component.spec.ts # src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.spec.ts # src/app/shared/comcol-forms/edit-comcol-page/comcol-role/comcol-role.component.ts # src/app/shared/dso-selector/dso-selector/dso-selector.component.ts # src/app/shared/dso-selector/modal-wrappers/export-metadata-selector/export-metadata-selector.component.ts # src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects.ts # src/app/shared/form/builder/form-builder.service.ts # src/app/shared/item/item-versions/item-versions.component.ts # src/app/shared/mocks/remote-data-build.service.mock.ts # src/app/shared/mydspace-actions/workspaceitem/workspaceitem-actions.component.ts # src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.spec.ts # src/app/shared/object-select/collection-select/collection-select.component.spec.ts # src/app/shared/object-select/item-select/item-select.component.spec.ts # src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.spec.ts # src/app/shared/pagination-drag-and-drop/abstract-paginated-drag-and-drop-list.component.ts # src/app/shared/resource-policies/form/eperson-group-list/eperson-group-list.component.ts # src/app/shared/search/search-query-response.model.ts # src/app/statistics/statistics.module.ts # src/app/submission/import-external/import-external-searchbar/submission-import-external-searchbar.component.spec.ts # src/app/submission/sections/form/section-form.component.ts # src/app/submission/sections/upload/section-upload.component.spec.ts # src/app/submission/sections/upload/section-upload.component.ts # src/app/submission/submission.service.spec.ts # src/app/submission/submission.service.ts
323 lines
14 KiB
TypeScript
323 lines
14 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { createSelector, Store } from '@ngrx/store';
|
|
|
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
|
import { filter, map, switchMap } from 'rxjs/operators';
|
|
|
|
import { AppState } from '../app.reducer';
|
|
import { CommunityDataService } from '../core/data/community-data.service';
|
|
import { FindListOptions } from '../core/data/request.models';
|
|
import { Community } from '../core/shared/community.model';
|
|
import { Collection } from '../core/shared/collection.model';
|
|
import { PageInfo } from '../core/shared/page-info.model';
|
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
|
import { RemoteData } from '../core/data/remote-data';
|
|
import { PaginatedList, buildPaginatedList } from '../core/data/paginated-list.model';
|
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
|
import { CommunityListSaveAction } from './community-list.actions';
|
|
import { CommunityListState } from './community-list.reducer';
|
|
import { getCommunityPageRoute } from '../+community-page/community-page-routing-paths';
|
|
import { getCollectionPageRoute } from '../+collection-page/collection-page-routing-paths';
|
|
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../core/shared/operators';
|
|
|
|
/**
|
|
* 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<boolean>;
|
|
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: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>
|
|
observableCombineLatest([...obsList]).pipe(
|
|
map((matrix: any[][]) => [].concat(...matrix)),
|
|
filter((arr: any[]) => arr.every((e) => hasValue(e))),
|
|
);
|
|
|
|
/**
|
|
* 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<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),
|
|
});
|
|
|
|
/**
|
|
* Creates a show More flatnode where only the level and parent are of importance
|
|
*/
|
|
export const showMoreFlatNode = (
|
|
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,
|
|
});
|
|
|
|
// 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);
|
|
|
|
export const MAX_COMCOLS_PER_PAGE = 50;
|
|
|
|
/**
|
|
* 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 {
|
|
|
|
constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService,
|
|
private store: Store<any>) {
|
|
}
|
|
|
|
saveCommunityListStateToStore(expandedNodes: FlatNode[], loadingNode: FlatNode): void {
|
|
this.store.dispatch(new CommunityListSaveAction(expandedNodes, loadingNode));
|
|
}
|
|
|
|
getExpandedNodesFromStore(): Observable<FlatNode[]> {
|
|
return this.store.select(expandedNodesSelector);
|
|
}
|
|
|
|
getLoadingNodeFromStore(): Observable<FlatNode> {
|
|
return this.store.select(loadingNodeSelector);
|
|
}
|
|
|
|
/**
|
|
* Gets all top communities, limited by page, and transforms this in a list of flatNodes.
|
|
* @param findOptions FindListOptions
|
|
* @param expandedNodes List of expanded nodes; if a node is not expanded its subCommunities and collections need
|
|
* not be added to the list
|
|
*/
|
|
loadCommunities(findOptions: FindListOptions, expandedNodes: FlatNode[]): Observable<FlatNode[]> {
|
|
const currentPage = findOptions.currentPage;
|
|
const topCommunities = [];
|
|
for (let i = 1; i <= currentPage; i++) {
|
|
const pagination: FindListOptions = Object.assign({}, findOptions, { currentPage: i });
|
|
topCommunities.push(this.getTopCommunities(pagination));
|
|
}
|
|
const topComs$ = observableCombineLatest([...topCommunities]).pipe(
|
|
map((coms: PaginatedList<Community>[]) => {
|
|
const newPages: Community[][] = coms.map((unit: PaginatedList<Community>) => unit.page);
|
|
const newPage: Community[] = [].concat(...newPages);
|
|
let newPageInfo = new PageInfo();
|
|
if (coms && coms.length > 0) {
|
|
newPageInfo = Object.assign({}, coms[0].pageInfo, { currentPage });
|
|
}
|
|
return buildPaginatedList(newPageInfo, newPage);
|
|
})
|
|
);
|
|
return topComs$.pipe(
|
|
switchMap((topComs: PaginatedList<Community>) => this.transformListOfCommunities(topComs, 0, null, expandedNodes)),
|
|
// distinctUntilChanged((a: FlatNode[], b: FlatNode[]) => a.length === b.length)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Puts the initial top level communities in a list to be called upon
|
|
*/
|
|
private getTopCommunities(options: FindListOptions): Observable<PaginatedList<Community>> {
|
|
return this.communityDataService.findTop({
|
|
currentPage: options.currentPage,
|
|
elementsPerPage: MAX_COMCOLS_PER_PAGE,
|
|
sort: {
|
|
field: options.sort.field,
|
|
direction: options.sort.direction
|
|
}
|
|
}).pipe(
|
|
getFirstSucceededRemoteData(),
|
|
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 = listOfPaginatedCommunities.currentPage;
|
|
if (isNotEmpty(parent)) {
|
|
currentPage = expandedNodes.find((node: FlatNode) => node.id === parent.id).currentCommunityPage;
|
|
}
|
|
let obsList = listOfPaginatedCommunities.page
|
|
.map((community: Community) => {
|
|
return this.transformCommunity(community, level, parent, expandedNodes);
|
|
});
|
|
if (currentPage < listOfPaginatedCommunities.totalPages && currentPage === listOfPaginatedCommunities.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<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: MAX_COMCOLS_PER_PAGE,
|
|
currentPage: i
|
|
})
|
|
.pipe(
|
|
getFirstCompletedRemoteData(),
|
|
switchMap((rd: RemoteData<PaginatedList<Community>>) => {
|
|
if (hasValue(rd) && hasValue(rd.payload)) {
|
|
return this.transformListOfCommunities(rd.payload, level + 1, communityFlatNode, expandedNodes);
|
|
} else {
|
|
return observableOf([]);
|
|
}
|
|
})
|
|
);
|
|
|
|
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: MAX_COMCOLS_PER_PAGE,
|
|
currentPage: i
|
|
})
|
|
.pipe(
|
|
getFirstCompletedRemoteData(),
|
|
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
|
if (hasValue(rd) && hasValue(rd.payload)) {
|
|
let nodes = rd.payload.page
|
|
.map((collection: Collection) => toFlatNode(collection, observableOf(false), level + 1, false, communityFlatNode));
|
|
if (currentCollectionPage < rd.payload.totalPages && currentCollectionPage === rd.payload.currentPage) {
|
|
nodes = [...nodes, showMoreFlatNode('collection', level + 1, communityFlatNode)];
|
|
}
|
|
return nodes;
|
|
} else {
|
|
return [];
|
|
}
|
|
}),
|
|
);
|
|
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(
|
|
map((rd: RemoteData<PaginatedList<Community>>) => {
|
|
if (hasValue(rd) && hasValue(rd.payload)) {
|
|
return rd.payload.totalElements > 0;
|
|
} else {
|
|
return false;
|
|
}
|
|
}),
|
|
);
|
|
|
|
hasColls$ = this.collectionDataService.findByParent(community.uuid, { elementsPerPage: 1 })
|
|
.pipe(
|
|
map((rd: RemoteData<PaginatedList<Collection>>) => {
|
|
if (hasValue(rd) && hasValue(rd.payload)) {
|
|
return rd.payload.totalElements > 0;
|
|
} else {
|
|
return false;
|
|
}
|
|
}),
|
|
);
|
|
|
|
let hasChildren$: Observable<boolean>;
|
|
hasChildren$ = observableCombineLatest(hasSubcoms$, hasColls$).pipe(
|
|
map(([hasSubcoms, hasColls]: [boolean, boolean]) => hasSubcoms || hasColls)
|
|
);
|
|
|
|
return hasChildren$;
|
|
}
|
|
|
|
}
|