Skip to content

Commit

Permalink
Merge remote-tracking branch 'alex/w2p-112970_added-missing-breadcrum…
Browse files Browse the repository at this point in the history
…bs_contribute-7.4' into dspace-7_x

# Conflicts:
#	src/app/collection-page/collection-page-routing.module.ts
#	src/app/community-page/community-page-routing.module.ts
#	src/app/core/core.module.ts
#	src/app/workflowitems-edit-page/workflowitems-edit-page-routing.module.ts
#	src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts
#	src/assets/i18n/en.json5
  • Loading branch information
alexandrevryghem committed Apr 19, 2024
2 parents 7a56943 + 4251630 commit c1ec793
Show file tree
Hide file tree
Showing 20 changed files with 306 additions and 34 deletions.
24 changes: 22 additions & 2 deletions src/app/collection-page/collection-page-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,33 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
import { CommunityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';

@NgModule({
imports: [
RouterModule.forChild([
{
path: COLLECTION_CREATE_PATH,
component: CreateCollectionPageComponent,
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard]
children: [
{
path: '',
component: CreateCollectionPageComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'collection.create',
},
},
],
canActivate: [AuthenticatedGuard, CreateCollectionPageGuard],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: CommunityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down Expand Up @@ -94,6 +113,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
LinkService,
CreateCollectionPageGuard,
CollectionPageAdministratorGuard,
CommunityBreadcrumbResolver,
]
})
export class CollectionPageRoutingModule {
Expand Down
23 changes: 21 additions & 2 deletions src/app/community-page/community-page-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,33 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedCommunityPageComponent } from './themed-community-page.component';
import { MenuItemType } from '../shared/menu/menu-item-type.model';
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';

@NgModule({
imports: [
RouterModule.forChild([
{
path: COMMUNITY_CREATE_PATH,
component: CreateCommunityPageComponent,
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard]
children: [
{
path: '',
component: CreateCommunityPageComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'community.create',
},
}
],
canActivate: [AuthenticatedGuard, CreateCommunityPageGuard],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: CommunityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down
21 changes: 21 additions & 0 deletions src/app/core/breadcrumbs/community-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { CommunityDataService } from '../data/community-data.service';
import { Community } from '../shared/community.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { hasValue } from '../../shared/empty.util';

/**
* The class that resolves the BreadcrumbConfig object for a Community
Expand All @@ -17,6 +21,23 @@ export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community
super(breadcrumbService, dataService);
}

/**
* Method to retrieve the breadcrumb config by the route id. It is also possible to retrieve the id through the
* query parameters. This is done by defining the name of the query parameter in the data section under the property
* breadcrumbQueryParam.
*
* @param route The current {@link ActivatedRouteSnapshot}
* @param state The current {@link RouterStateSnapshot}
* @returns BreadcrumbConfig object
*/
override resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<Community>> {
if (hasValue(route.data.breadcrumbQueryParam) && hasValue(route.queryParams[route.data.breadcrumbQueryParam])) {
return this.resolveById(route.queryParams[route.data.breadcrumbQueryParam]);
} else {
return super.resolve(route, state);
}
}

/**
* Method that returns the follow links to already resolve
* The self links defined in this list are expected to be requested somewhere in the near future
Expand Down
5 changes: 4 additions & 1 deletion src/app/core/breadcrumbs/dso-breadcrumb.resolver.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ describe('DSOBreadcrumbResolver', () => {
uuid = '1234-65487-12354-1235';
breadcrumbUrl = '/collections/' + uuid;
currentUrl = breadcrumbUrl + '/edit';
testCollection = Object.assign(new Collection(), { uuid });
testCollection = Object.assign(new Collection(), {
uuid: uuid,
type: 'collection',
});
dsoBreadcrumbService = {};
collectionService = {
findById: (id: string) => createSuccessfulRemoteDataObject$(testCollection)
Expand Down
16 changes: 12 additions & 4 deletions src/app/core/breadcrumbs/dso-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ChildHALResource } from '../shared/child-hal-resource.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { hasValue } from '../../shared/empty.util';
import { IdentifiableDataService } from '../data/base/identifiable-data.service';
import { getDSORoute } from '../../app-routing-paths';

/**
* The class that resolves the BreadcrumbConfig object for a DSpaceObject
Expand All @@ -31,15 +32,22 @@ export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceO
* @returns BreadcrumbConfig object
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
const uuid = route.params.id;
return this.resolveById(route.params.id);
}

/**
* Method for resolving a breadcrumb by id
*
* @param uuid The uuid to resolve
* @returns BreadcrumbConfig object
*/
resolveById(uuid: string): Observable<BreadcrumbConfig<T>> {
return this.dataService.findById(uuid, true, false, ...this.followLinks).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((object: T) => {
if (hasValue(object)) {
const fullPath = state.url;
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
return { provider: this.breadcrumbService, key: object, url: url };
return { provider: this.breadcrumbService, key: object, url: getDSORoute(object) };
} else {
return undefined;
}
Expand Down
2 changes: 2 additions & 0 deletions src/app/core/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ import { FlatBrowseDefinition } from './shared/flat-browse-definition.model';
import { ValueListBrowseDefinition } from './shared/value-list-browse-definition.model';
import { NonHierarchicalBrowseDefinition } from './shared/non-hierarchical-browse-definition';
import { BulkAccessConditionOptions } from './config/models/bulk-access-condition-options.model';
import { SubmissionParentBreadcrumbsService } from './submission/submission-parent-breadcrumb.service';

/**
* When not in production, endpoint responses can be mocked for testing purposes
Expand Down Expand Up @@ -245,6 +246,7 @@ const PROVIDERS = [
NotificationsService,
WorkspaceitemDataService,
WorkflowItemDataService,
SubmissionParentBreadcrumbsService,
DSpaceObjectDataService,
ConfigurationDataService,
DSOChangeAnalyzer,
Expand Down
14 changes: 14 additions & 0 deletions src/app/core/submission/resolver/submission-links-to-follow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { followLink, FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
import { WorkflowItem } from '../models/workflowitem.model';
import { WorkspaceItem } from '../models/workspaceitem.model';

/**
* The self links defined in this list are expected to be requested somewhere in the near future
* Requesting them as embeds will limit the number of requests
*
* Needs to be in a separate file to prevent circular dependencies in webpack.
*/
export const SUBMISSION_LINKS_TO_FOLLOW: FollowLinkConfig<WorkflowItem | WorkspaceItem>[] = [
followLink('item'),
followLink('collection'),
];
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { followLink } from '../../../shared/utils/follow-link-config.model';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { switchMap } from 'rxjs/operators';
import { RemoteData } from '../../data/remote-data';
import { getFirstCompletedRemoteData } from '../../shared/operators';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';

/**
* This class represents a resolver that requests a specific item before the route is activated
Expand All @@ -15,7 +14,6 @@ import { IdentifiableDataService } from '../../data/base/identifiable-data.servi
export class SubmissionObjectResolver<T> implements Resolve<RemoteData<T>> {
constructor(
protected dataService: IdentifiableDataService<any>,
protected store: Store<any>,
) {
}

Expand All @@ -30,7 +28,7 @@ export class SubmissionObjectResolver<T> implements Resolve<RemoteData<T>> {
const itemRD$ = this.dataService.findById(route.params.id,
true,
false,
followLink('item'),
...SUBMISSION_LINKS_TO_FOLLOW,
).pipe(
getFirstCompletedRemoteData(),
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<T>>),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../shared/operators';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import { BreadcrumbConfig } from '../../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { SubmissionParentBreadcrumbsService } from '../submission-parent-breadcrumb.service';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';
import { SubmissionObject } from '../models/submission-object.model';

/**
* This class represents a resolver that requests a specific item before the route is activated
*/
export abstract class SubmissionParentBreadcrumbResolver implements Resolve<BreadcrumbConfig<SubmissionObject>> {

protected constructor(
protected dataService: IdentifiableDataService<any>,
protected breadcrumbService: SubmissionParentBreadcrumbsService,
) {
}

/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<SubmissionObject>> {
return this.dataService.findById(route.params.id,
true,
false,
...SUBMISSION_LINKS_TO_FOLLOW,
).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((submissionObject: SubmissionObject) => ({
provider: this.breadcrumbService,
key: submissionObject,
} as BreadcrumbConfig<SubmissionObject>)),
);
}
}
59 changes: 59 additions & 0 deletions src/app/core/submission/submission-parent-breadcrumb.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { BreadcrumbsProviderService } from '../breadcrumbs/breadcrumbsProviderService';
import { Injectable } from '@angular/core';
import { Observable, switchMap, combineLatest, of as observableOf } from 'rxjs';
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../shared/operators';
import { Collection } from '../shared/collection.model';
import { DSONameService } from '../breadcrumbs/dso-name.service';
import { SubmissionObject } from './models/submission-object.model';
import { RemoteData } from '../data/remote-data';
import { DSOBreadcrumbsService } from '../breadcrumbs/dso-breadcrumbs.service';
import { getDSORoute } from '../../app-routing-paths';
import { SubmissionService } from '../../submission/submission.service';
import { CollectionDataService } from '../data/collection-data.service';
import { hasValue } from '../../shared/empty.util';

/**
* Service to calculate the parent {@link DSpaceObject} breadcrumbs for a {@link SubmissionObject}
*/
@Injectable()
export class SubmissionParentBreadcrumbsService implements BreadcrumbsProviderService<SubmissionObject> {

constructor(
protected dsoNameService: DSONameService,
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
protected submissionService: SubmissionService,
protected collectionService: CollectionDataService,
) {
}

/**
* Creates the parent breadcrumb structure for {@link SubmissionObject}s. It also automatically recreates the
* parent breadcrumb structure when you change a {@link SubmissionObject}'s by dispatching a
* {@link ChangeSubmissionCollectionAction}.
*
* @param submissionObject The {@link SubmissionObject} for which the parent breadcrumb structure needs to be created
*/
getBreadcrumbs(submissionObject: SubmissionObject): Observable<Breadcrumb[]> {
return combineLatest([
(submissionObject.collection as Observable<RemoteData<Collection>>).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
),
this.submissionService.getSubmissionCollectionId(submissionObject.id),
]).pipe(
switchMap(([collection, latestCollectionId]: [Collection, string]) => {
if (hasValue(latestCollectionId)) {
return this.collectionService.findById(latestCollectionId).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
);
} else {
return observableOf(collection);
}
}),
switchMap((collection: Collection) => this.dsoBreadcrumbsService.getBreadcrumbs(collection, getDSORoute(collection))),
);
}

}
27 changes: 25 additions & 2 deletions src/app/submission/submission.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Router } from '@angular/router';

import { Observable, of as observableOf, Subscription, timer as observableTimer } from 'rxjs';
import { catchError, concatMap, distinctUntilChanged, filter, find, map, startWith, take, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Store, MemoizedSelector, createSelector, select } from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';

import { submissionSelector, SubmissionState } from './submission.reducers';
Expand Down Expand Up @@ -47,6 +47,20 @@ import { SubmissionJsonPatchOperationsService } from '../core/submission/submiss
import { SubmissionSectionObject } from './objects/submission-section-object.model';
import { SubmissionError } from './objects/submission-error.model';

function getSubmissionSelector(submissionId: string): MemoizedSelector<SubmissionState, SubmissionObjectEntry> {
return createSelector(
submissionSelector,
(state: SubmissionState) => state.objects[submissionId],
);
}

function getSubmissionCollectionIdSelector(submissionId: string): MemoizedSelector<SubmissionState, string> {
return createSelector(
getSubmissionSelector(submissionId),
(submission: SubmissionObjectEntry) => submission?.collection,
);
}

/**
* A service that provides methods used in submission process.
*/
Expand Down Expand Up @@ -96,10 +110,19 @@ export class SubmissionService {
* @param collectionId
* The collection id
*/
changeSubmissionCollection(submissionId, collectionId) {
changeSubmissionCollection(submissionId: string, collectionId: string): void {
this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId));
}

/**
* Listen to collection changes for a certain {@link SubmissionObject}
*
* @param submissionId The submission id
*/
getSubmissionCollectionId(submissionId: string): Observable<string> {
return this.store.pipe(select(getSubmissionCollectionIdSelector(submissionId)));
}

/**
* Perform a REST call to create a new workspaceitem and return response
*
Expand Down
Loading

0 comments on commit c1ec793

Please sign in to comment.