Skip to content

Commit 489d8fd

Browse files
authored
Merge pull request DSpace#2888 from alexandrevryghem/w2p-112970_added-missing-breadcrumbs_contribute-main
Added missing/incomplete breadcrumbs on create community/collection/item pages
2 parents 57b0efe + 97f70d8 commit 489d8fd

15 files changed

Lines changed: 324 additions & 22 deletions

src/app/collection-page/collection-page-routes.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { browseByGuard } from '../browse-by/browse-by-guard';
77
import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
88
import { authenticatedGuard } from '../core/auth/authenticated.guard';
99
import { collectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
10+
import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
1011
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
1112
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
1213
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
@@ -27,12 +28,29 @@ import { itemTemplatePageResolver } from './edit-item-template-page/item-templat
2728
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
2829
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
2930

30-
3131
export const ROUTES: Route[] = [
3232
{
3333
path: COLLECTION_CREATE_PATH,
34-
component: CreateCollectionPageComponent,
3534
canActivate: [authenticatedGuard, createCollectionPageGuard],
35+
children: [
36+
{
37+
path: '',
38+
component: CreateCollectionPageComponent,
39+
resolve: {
40+
breadcrumb: i18nBreadcrumbResolver,
41+
},
42+
data: {
43+
breadcrumbKey: 'collection.create',
44+
},
45+
},
46+
],
47+
data: {
48+
breadcrumbQueryParam: 'parent',
49+
},
50+
resolve: {
51+
breadcrumb: communityBreadcrumbResolver,
52+
},
53+
runGuardsAndResolvers: 'always',
3654
},
3755
{
3856
path: ':id',

src/app/community-page/community-page-routes.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,26 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
2828
export const ROUTES: Route[] = [
2929
{
3030
path: COMMUNITY_CREATE_PATH,
31-
component: CreateCommunityPageComponent,
31+
children: [
32+
{
33+
path: '',
34+
component: CreateCommunityPageComponent,
35+
resolve: {
36+
breadcrumb: i18nBreadcrumbResolver,
37+
},
38+
data: {
39+
breadcrumbKey: 'community.create',
40+
},
41+
},
42+
],
3243
canActivate: [authenticatedGuard, createCommunityPageGuard],
44+
data: {
45+
breadcrumbQueryParam: 'parent',
46+
},
47+
resolve: {
48+
breadcrumb: communityBreadcrumbResolver,
49+
},
50+
runGuardsAndResolvers: 'always',
3351
},
3452
{
3553
path: ':id',

src/app/core/breadcrumbs/community-breadcrumb.resolver.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,15 @@ import { Observable } from 'rxjs';
88

99
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
1010
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
11+
import { hasValue } from '../../shared/empty.util';
1112
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
1213
import { CommunityDataService } from '../data/community-data.service';
1314
import { Community } from '../shared/community.model';
1415
import { DSpaceObject } from '../shared/dspace-object.model';
15-
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
16+
import {
17+
DSOBreadcrumbResolver,
18+
DSOBreadcrumbResolverByUuid,
19+
} from './dso-breadcrumb.resolver';
1620
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
1721

1822
/**
@@ -25,11 +29,22 @@ export const communityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>>
2529
dataService: CommunityDataService = inject(CommunityDataService),
2630
): Observable<BreadcrumbConfig<Community>> => {
2731
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
28-
return DSOBreadcrumbResolver(
29-
route,
30-
state,
31-
breadcrumbService,
32-
dataService,
33-
...linksToFollow,
34-
) as Observable<BreadcrumbConfig<Community>>;
32+
if (hasValue(route.data.breadcrumbQueryParam) && hasValue(route.queryParams[route.data.breadcrumbQueryParam])) {
33+
return DSOBreadcrumbResolverByUuid(
34+
route,
35+
state,
36+
route.queryParams[route.data.breadcrumbQueryParam],
37+
breadcrumbService,
38+
dataService,
39+
...linksToFollow,
40+
) as Observable<BreadcrumbConfig<Community>>;
41+
} else {
42+
return DSOBreadcrumbResolver(
43+
route,
44+
state,
45+
breadcrumbService,
46+
dataService,
47+
...linksToFollow,
48+
) as Observable<BreadcrumbConfig<Community>>;
49+
}
3550
};

src/app/core/breadcrumbs/dso-breadcrumb.resolver.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ describe('DSOBreadcrumbResolver', () => {
1818
uuid = '1234-65487-12354-1235';
1919
breadcrumbUrl = `/collections/${uuid}`;
2020
currentUrl = `${breadcrumbUrl}/edit`;
21-
testCollection = Object.assign(new Collection(), { uuid });
21+
testCollection = Object.assign(new Collection(), {
22+
uuid: uuid,
23+
type: 'collection',
24+
});
2225
dsoBreadcrumbService = {};
2326
collectionService = {
2427
findById: () => createSuccessfulRemoteDataObject$(testCollection),

src/app/core/breadcrumbs/dso-breadcrumb.resolver.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
import { Observable } from 'rxjs';
66
import { map } from 'rxjs/operators';
77

8+
import { getDSORoute } from '../../app-routing-paths';
89
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
910
import { hasValue } from '../../shared/empty.util';
1011
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
@@ -32,15 +33,34 @@ export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: Route
3233
dataService: IdentifiableDataService<DSpaceObject>,
3334
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
3435
): Observable<BreadcrumbConfig<DSpaceObject>> => {
35-
const uuid = route.params.id;
36+
return DSOBreadcrumbResolverByUuid(route, state, route.params.id, breadcrumbService, dataService, ...linksToFollow);
37+
};
38+
39+
/**
40+
* Method for resolving a breadcrumb config object with the given UUID
41+
*
42+
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
43+
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
44+
* @param {String} uuid The uuid of the DSO object
45+
* @param {DSOBreadcrumbsService} breadcrumbService
46+
* @param {IdentifiableDataService} dataService
47+
* @param linksToFollow
48+
* @returns BreadcrumbConfig object
49+
*/
50+
export const DSOBreadcrumbResolverByUuid: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, uuid: string, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
51+
route: ActivatedRouteSnapshot,
52+
state: RouterStateSnapshot,
53+
uuid: string,
54+
breadcrumbService: DSOBreadcrumbsService,
55+
dataService: IdentifiableDataService<DSpaceObject>,
56+
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
57+
): Observable<BreadcrumbConfig<DSpaceObject>> => {
3658
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
3759
getFirstCompletedRemoteData(),
3860
getRemoteDataPayload(),
3961
map((object: DSpaceObject) => {
4062
if (hasValue(object)) {
41-
const fullPath = state.url;
42-
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
43-
return { provider: breadcrumbService, key: object, url: url };
63+
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
4464
} else {
4565
return undefined;
4666
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
followLink,
3+
FollowLinkConfig,
4+
} from '../../../shared/utils/follow-link-config.model';
5+
import { WorkflowItem } from '../models/workflowitem.model';
6+
import { WorkspaceItem } from '../models/workspaceitem.model';
7+
8+
/**
9+
* The self links defined in this list are expected to be requested somewhere in the near future
10+
* Requesting them as embeds will limit the number of requests
11+
*
12+
* Needs to be in a separate file to prevent circular dependencies in webpack.
13+
*/
14+
export const SUBMISSION_LINKS_TO_FOLLOW: FollowLinkConfig<WorkflowItem | WorkspaceItem>[] = [
15+
followLink('item'),
16+
followLink('collection'),
17+
];

src/app/core/submission/resolver/submission-object.resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
import { Observable } from 'rxjs';
66
import { switchMap } from 'rxjs/operators';
77

8-
import { followLink } from '../../../shared/utils/follow-link-config.model';
98
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
109
import { RemoteData } from '../../data/remote-data';
1110
import { Item } from '../../shared/item.model';
1211
import { getFirstCompletedRemoteData } from '../../shared/operators';
1312
import { SubmissionObject } from '../models/submission-object.model';
13+
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';
1414

1515
/**
1616
* Method for resolving an item based on the parameters in the current route
@@ -28,7 +28,7 @@ export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: Ro
2828
return dataService.findById(route.params.id,
2929
true,
3030
false,
31-
followLink('item'),
31+
...SUBMISSION_LINKS_TO_FOLLOW,
3232
).pipe(
3333
getFirstCompletedRemoteData(),
3434
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
ActivatedRouteSnapshot,
3+
Resolve,
4+
RouterStateSnapshot,
5+
} from '@angular/router';
6+
import { Observable } from 'rxjs';
7+
import { map } from 'rxjs/operators';
8+
9+
import { BreadcrumbConfig } from '../../../breadcrumbs/breadcrumb/breadcrumb-config.model';
10+
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
11+
import {
12+
getFirstCompletedRemoteData,
13+
getRemoteDataPayload,
14+
} from '../../shared/operators';
15+
import { SubmissionObject } from '../models/submission-object.model';
16+
import { SubmissionParentBreadcrumbsService } from '../submission-parent-breadcrumb.service';
17+
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';
18+
19+
/**
20+
* This class represents a resolver that requests a specific item before the route is activated
21+
*/
22+
export abstract class SubmissionParentBreadcrumbResolver implements Resolve<BreadcrumbConfig<SubmissionObject>> {
23+
24+
protected constructor(
25+
protected dataService: IdentifiableDataService<any>,
26+
protected breadcrumbService: SubmissionParentBreadcrumbsService,
27+
) {
28+
}
29+
30+
/**
31+
* Method for resolving an item based on the parameters in the current route
32+
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
33+
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
34+
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
35+
* or an error if something went wrong
36+
*/
37+
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<SubmissionObject>> {
38+
return this.dataService.findById(route.params.id,
39+
true,
40+
false,
41+
...SUBMISSION_LINKS_TO_FOLLOW,
42+
).pipe(
43+
getFirstCompletedRemoteData(),
44+
getRemoteDataPayload(),
45+
map((submissionObject: SubmissionObject) => ({
46+
provider: this.breadcrumbService,
47+
key: submissionObject,
48+
} as BreadcrumbConfig<SubmissionObject>)),
49+
);
50+
}
51+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Injectable } from '@angular/core';
2+
import {
3+
combineLatest,
4+
Observable,
5+
of as observableOf,
6+
switchMap,
7+
} from 'rxjs';
8+
9+
import { getDSORoute } from '../../app-routing-paths';
10+
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
11+
import { hasValue } from '../../shared/empty.util';
12+
import { SubmissionService } from '../../submission/submission.service';
13+
import { BreadcrumbsProviderService } from '../breadcrumbs/breadcrumbsProviderService';
14+
import { DSOBreadcrumbsService } from '../breadcrumbs/dso-breadcrumbs.service';
15+
import { DSONameService } from '../breadcrumbs/dso-name.service';
16+
import { CollectionDataService } from '../data/collection-data.service';
17+
import { RemoteData } from '../data/remote-data';
18+
import { Collection } from '../shared/collection.model';
19+
import {
20+
getFirstCompletedRemoteData,
21+
getRemoteDataPayload,
22+
} from '../shared/operators';
23+
import { SubmissionObject } from './models/submission-object.model';
24+
25+
/**
26+
* Service to calculate the parent {@link DSpaceObject} breadcrumbs for a {@link SubmissionObject}
27+
*/
28+
@Injectable({
29+
providedIn: 'root',
30+
})
31+
export class SubmissionParentBreadcrumbsService implements BreadcrumbsProviderService<SubmissionObject> {
32+
33+
constructor(
34+
protected dsoNameService: DSONameService,
35+
protected dsoBreadcrumbsService: DSOBreadcrumbsService,
36+
protected submissionService: SubmissionService,
37+
protected collectionService: CollectionDataService,
38+
) {
39+
}
40+
41+
/**
42+
* Creates the parent breadcrumb structure for {@link SubmissionObject}s. It also automatically recreates the
43+
* parent breadcrumb structure when you change a {@link SubmissionObject}'s by dispatching a
44+
* {@link ChangeSubmissionCollectionAction}.
45+
*
46+
* @param submissionObject The {@link SubmissionObject} for which the parent breadcrumb structure needs to be created
47+
*/
48+
getBreadcrumbs(submissionObject: SubmissionObject): Observable<Breadcrumb[]> {
49+
return combineLatest([
50+
(submissionObject.collection as Observable<RemoteData<Collection>>).pipe(
51+
getFirstCompletedRemoteData(),
52+
getRemoteDataPayload(),
53+
),
54+
this.submissionService.getSubmissionCollectionId(submissionObject.id),
55+
]).pipe(
56+
switchMap(([collection, latestCollectionId]: [Collection, string]) => {
57+
if (hasValue(latestCollectionId)) {
58+
return this.collectionService.findById(latestCollectionId).pipe(
59+
getFirstCompletedRemoteData(),
60+
getRemoteDataPayload(),
61+
);
62+
} else {
63+
return observableOf(collection);
64+
}
65+
}),
66+
switchMap((collection: Collection) => this.dsoBreadcrumbsService.getBreadcrumbs(collection, getDSORoute(collection))),
67+
);
68+
}
69+
70+
}

src/app/submission/submission.service.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { HttpHeaders } from '@angular/common/http';
22
import { Injectable } from '@angular/core';
33
import { Router } from '@angular/router';
4-
import { Store } from '@ngrx/store';
4+
import {
5+
createSelector,
6+
MemoizedSelector,
7+
select,
8+
Store,
9+
} from '@ngrx/store';
510
import { TranslateService } from '@ngx-translate/core';
611
import {
712
Observable,
@@ -71,6 +76,20 @@ import {
7176
SubmissionState,
7277
} from './submission.reducers';
7378

79+
function getSubmissionSelector(submissionId: string): MemoizedSelector<SubmissionState, SubmissionObjectEntry> {
80+
return createSelector(
81+
submissionSelector,
82+
(state: SubmissionState) => state.objects[submissionId],
83+
);
84+
}
85+
86+
function getSubmissionCollectionIdSelector(submissionId: string): MemoizedSelector<SubmissionState, string> {
87+
return createSelector(
88+
getSubmissionSelector(submissionId),
89+
(submission: SubmissionObjectEntry) => submission?.collection,
90+
);
91+
}
92+
7493
/**
7594
* A service that provides methods used in submission process.
7695
*/
@@ -120,10 +139,19 @@ export class SubmissionService {
120139
* @param collectionId
121140
* The collection id
122141
*/
123-
changeSubmissionCollection(submissionId, collectionId) {
142+
changeSubmissionCollection(submissionId: string, collectionId: string): void {
124143
this.store.dispatch(new ChangeSubmissionCollectionAction(submissionId, collectionId));
125144
}
126145

146+
/**
147+
* Listen to collection changes for a certain {@link SubmissionObject}
148+
*
149+
* @param submissionId The submission id
150+
*/
151+
getSubmissionCollectionId(submissionId: string): Observable<string> {
152+
return this.store.pipe(select(getSubmissionCollectionIdSelector(submissionId)));
153+
}
154+
127155
/**
128156
* Perform a REST call to create a new workspaceitem and return response
129157
*

0 commit comments

Comments
 (0)