Skip to content

Commit

Permalink
Merge pull request DSpace#2888 from alexandrevryghem/w2p-112970_added…
Browse files Browse the repository at this point in the history
…-missing-breadcrumbs_contribute-main

Added missing/incomplete breadcrumbs on create community/collection/item pages
  • Loading branch information
tdonohue authored Apr 19, 2024
2 parents 57b0efe + 97f70d8 commit 489d8fd
Show file tree
Hide file tree
Showing 15 changed files with 324 additions and 22 deletions.
22 changes: 20 additions & 2 deletions src/app/collection-page/collection-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { browseByGuard } from '../browse-by/browse-by-guard';
import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
import { authenticatedGuard } from '../core/auth/authenticated.guard';
import { collectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
import { communityBreadcrumbResolver } from '../core/breadcrumbs/community-breadcrumb.resolver';
import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
import { ComcolSearchSectionComponent } from '../shared/comcol/sections/comcol-search-section/comcol-search-section.component';
Expand All @@ -27,12 +28,29 @@ import { itemTemplatePageResolver } from './edit-item-template-page/item-templat
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
import { ThemedCollectionPageComponent } from './themed-collection-page.component';


export const ROUTES: Route[] = [
{
path: COLLECTION_CREATE_PATH,
component: CreateCollectionPageComponent,
canActivate: [authenticatedGuard, createCollectionPageGuard],
children: [
{
path: '',
component: CreateCollectionPageComponent,
resolve: {
breadcrumb: i18nBreadcrumbResolver,
},
data: {
breadcrumbKey: 'collection.create',
},
},
],
data: {
breadcrumbQueryParam: 'parent',
},
resolve: {
breadcrumb: communityBreadcrumbResolver,
},
runGuardsAndResolvers: 'always',
},
{
path: ':id',
Expand Down
20 changes: 19 additions & 1 deletion src/app/community-page/community-page-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,26 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
export const ROUTES: Route[] = [
{
path: COMMUNITY_CREATE_PATH,
component: CreateCommunityPageComponent,
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
31 changes: 23 additions & 8 deletions src/app/core/breadcrumbs/community-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import { Observable } from 'rxjs';

import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../community-page/community-page.resolver';
import { hasValue } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { CommunityDataService } from '../data/community-data.service';
import { Community } from '../shared/community.model';
import { DSpaceObject } from '../shared/dspace-object.model';
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
import {
DSOBreadcrumbResolver,
DSOBreadcrumbResolverByUuid,
} from './dso-breadcrumb.resolver';
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';

/**
Expand All @@ -25,11 +29,22 @@ export const communityBreadcrumbResolver: ResolveFn<BreadcrumbConfig<Community>>
dataService: CommunityDataService = inject(CommunityDataService),
): Observable<BreadcrumbConfig<Community>> => {
const linksToFollow: FollowLinkConfig<DSpaceObject>[] = COMMUNITY_PAGE_LINKS_TO_FOLLOW as FollowLinkConfig<DSpaceObject>[];
return DSOBreadcrumbResolver(
route,
state,
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
if (hasValue(route.data.breadcrumbQueryParam) && hasValue(route.queryParams[route.data.breadcrumbQueryParam])) {
return DSOBreadcrumbResolverByUuid(
route,
state,
route.queryParams[route.data.breadcrumbQueryParam],
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
} else {
return DSOBreadcrumbResolver(
route,
state,
breadcrumbService,
dataService,
...linksToFollow,
) as Observable<BreadcrumbConfig<Community>>;
}
};
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: () => createSuccessfulRemoteDataObject$(testCollection),
Expand Down
28 changes: 24 additions & 4 deletions src/app/core/breadcrumbs/dso-breadcrumb.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { getDSORoute } from '../../app-routing-paths';
import { BreadcrumbConfig } from '../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { hasValue } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
Expand Down Expand Up @@ -32,15 +33,34 @@ export const DSOBreadcrumbResolver: (route: ActivatedRouteSnapshot, state: Route
dataService: IdentifiableDataService<DSpaceObject>,
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
): Observable<BreadcrumbConfig<DSpaceObject>> => {
const uuid = route.params.id;
return DSOBreadcrumbResolverByUuid(route, state, route.params.id, breadcrumbService, dataService, ...linksToFollow);
};

/**
* Method for resolving a breadcrumb config object with the given UUID
*
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @param {String} uuid The uuid of the DSO object
* @param {DSOBreadcrumbsService} breadcrumbService
* @param {IdentifiableDataService} dataService
* @param linksToFollow
* @returns BreadcrumbConfig object
*/
export const DSOBreadcrumbResolverByUuid: (route: ActivatedRouteSnapshot, state: RouterStateSnapshot, uuid: string, breadcrumbService: DSOBreadcrumbsService, dataService: IdentifiableDataService<DSpaceObject>, ...linksToFollow: FollowLinkConfig<DSpaceObject>[]) => Observable<BreadcrumbConfig<DSpaceObject>> = (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
uuid: string,
breadcrumbService: DSOBreadcrumbsService,
dataService: IdentifiableDataService<DSpaceObject>,
...linksToFollow: FollowLinkConfig<DSpaceObject>[]
): Observable<BreadcrumbConfig<DSpaceObject>> => {
return dataService.findById(uuid, true, false, ...linksToFollow).pipe(
getFirstCompletedRemoteData(),
getRemoteDataPayload(),
map((object: DSpaceObject) => {
if (hasValue(object)) {
const fullPath = state.url;
const url = (fullPath.substring(0, fullPath.indexOf(uuid))).concat(uuid);
return { provider: breadcrumbService, key: object, url: url };
return { provider: breadcrumbService, key: object, url: getDSORoute(object) };
} else {
return undefined;
}
Expand Down
17 changes: 17 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,17 @@
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
Expand Up @@ -5,12 +5,12 @@ import {
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

import { followLink } from '../../../shared/utils/follow-link-config.model';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import { RemoteData } from '../../data/remote-data';
import { Item } from '../../shared/item.model';
import { getFirstCompletedRemoteData } from '../../shared/operators';
import { SubmissionObject } from '../models/submission-object.model';
import { SUBMISSION_LINKS_TO_FOLLOW } from './submission-links-to-follow';

/**
* Method for resolving an item based on the parameters in the current route
Expand All @@ -28,7 +28,7 @@ export const SubmissionObjectResolver: (route: ActivatedRouteSnapshot, state: Ro
return 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<Item>>),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
ActivatedRouteSnapshot,
Resolve,
RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { BreadcrumbConfig } from '../../../breadcrumbs/breadcrumb/breadcrumb-config.model';
import { IdentifiableDataService } from '../../data/base/identifiable-data.service';
import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../../shared/operators';
import { SubmissionObject } from '../models/submission-object.model';
import { SubmissionParentBreadcrumbsService } from '../submission-parent-breadcrumb.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
*/
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>)),
);
}
}
70 changes: 70 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,70 @@
import { Injectable } from '@angular/core';
import {
combineLatest,
Observable,
of as observableOf,
switchMap,
} from 'rxjs';

import { getDSORoute } from '../../app-routing-paths';
import { Breadcrumb } from '../../breadcrumbs/breadcrumb/breadcrumb.model';
import { hasValue } from '../../shared/empty.util';
import { SubmissionService } from '../../submission/submission.service';
import { BreadcrumbsProviderService } from '../breadcrumbs/breadcrumbsProviderService';
import { DSOBreadcrumbsService } from '../breadcrumbs/dso-breadcrumbs.service';
import { DSONameService } from '../breadcrumbs/dso-name.service';
import { CollectionDataService } from '../data/collection-data.service';
import { RemoteData } from '../data/remote-data';
import { Collection } from '../shared/collection.model';
import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../shared/operators';
import { SubmissionObject } from './models/submission-object.model';

/**
* Service to calculate the parent {@link DSpaceObject} breadcrumbs for a {@link SubmissionObject}
*/
@Injectable({
providedIn: 'root',
})
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))),
);
}

}
32 changes: 30 additions & 2 deletions src/app/submission/submission.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import {
createSelector,
MemoizedSelector,
select,
Store,
} from '@ngrx/store';
import { TranslateService } from '@ngx-translate/core';
import {
Observable,
Expand Down Expand Up @@ -71,6 +76,20 @@ import {
SubmissionState,
} from './submission.reducers';

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 @@ -120,10 +139,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 489d8fd

Please sign in to comment.