diff --git a/src/app/features/contributors/contributors.component.ts b/src/app/features/contributors/contributors.component.ts index 46ae814a1..7fef58287 100644 --- a/src/app/features/contributors/contributors.component.ts +++ b/src/app/features/contributors/contributors.component.ts @@ -49,6 +49,7 @@ import { AcceptRequestAccess, AddContributor, BulkAddContributors, + BulkAddContributorsFromParentProject, BulkUpdateContributors, ContributorsSelectors, CreateViewOnlyLink, @@ -155,6 +156,7 @@ export class ContributorsComponent implements OnInit { deleteContributor: DeleteContributor, bulkUpdateContributors: BulkUpdateContributors, bulkAddContributors: BulkAddContributors, + bulkAddContributorsFromParentProject: BulkAddContributorsFromParentProject, addContributor: AddContributor, createViewOnlyLink: CreateViewOnlyLink, deleteViewOnlyLink: DeleteViewOnlyLink, @@ -242,17 +244,19 @@ export class ContributorsComponent implements OnInit { openAddContributorDialog() { const addedContributorIds = this.initialContributors().map((x) => x.userId); - const rootParentId = this.resourceDetails().rootParentId ?? this.resourceId(); + const resourceDetails = this.resourceDetails(); + const resourceId = this.resourceId(); + const rootParentId = resourceDetails.rootParentId ?? resourceId; this.loaderService.show(); this.actions - .getResourceWithChildren(rootParentId, this.resourceId(), this.resourceType()) + .getResourceWithChildren(rootParentId, resourceId, this.resourceType()) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { this.loaderService.hide(); - const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), this.resourceId()); + const components = this.mapNodesToComponentCheckboxItems(this.resourceChildren(), resourceId); this.customDialogService .open(AddContributorDialogComponent, { @@ -261,7 +265,11 @@ export class ContributorsComponent implements OnInit { data: { addedContributorIds, components, - resourceName: this.resourceDetails().title, + resourceName: resourceDetails.title, + allowAddingContributorsFromParentProject: + this.resourceType() === ResourceType.Project && + resourceDetails.rootParentId && + resourceDetails.rootParentId !== resourceId, }, }) .onClose.pipe( @@ -269,7 +277,9 @@ export class ContributorsComponent implements OnInit { takeUntilDestroyed(this.destroyRef) ) .subscribe((res: ContributorDialogAddModel) => { - if (res.type === AddContributorType.Unregistered) { + if (res.type === AddContributorType.ParentProject) { + this.addContributorsFromParentProjectToComponents(); + } else if (res.type === AddContributorType.Unregistered) { this.openAddUnregisteredContributorDialog(); } else { this.addContributorsToComponents(res); @@ -299,6 +309,13 @@ export class ContributorsComponent implements OnInit { .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage')); } + private addContributorsFromParentProjectToComponents(): void { + this.actions + .bulkAddContributorsFromParentProject(this.resourceId(), this.resourceType()) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(() => this.toastService.showSuccess('project.contributors.toastMessages.multipleAddSuccessMessage')); + } + openAddUnregisteredContributorDialog() { this.customDialogService .open(AddUnregisteredContributorDialogComponent, { diff --git a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts index c94ee8457..d20676f16 100644 --- a/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts +++ b/src/app/features/preprints/components/stepper/preprints-metadata-step/preprints-contributors/preprints-contributors.component.ts @@ -127,7 +127,7 @@ export class PreprintsContributorsComponent implements OnInit { .open(AddContributorDialogComponent, { header: 'project.contributors.addDialog.addRegisteredContributor', width: '448px', - data: addedContributorIds, + data: { addedContributorIds, allowAddingContributorsFromParentProject: true }, }) .onClose.pipe( filter((res: ContributorDialogAddModel) => !!res), diff --git a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html index 933bf2479..bd299f5ab 100644 --- a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html +++ b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.html @@ -65,6 +65,22 @@ } + @if (allowAddingContributorsFromParentProject() && this.isSearchState()) { +
+ + +
+ } +
([]); readonly components = signal([]); readonly resourceName = signal(''); + readonly allowAddingContributorsFromParentProject = signal(false); readonly contributorNames = computed(() => this.selectedUsers() @@ -113,6 +118,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { } } + addSourceProjectContributors(): void { + this.closeDialogWithData(AddContributorType.ParentProject); + } + addUnregistered(): void { this.dialogRef.close({ data: [], @@ -129,7 +138,7 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { private initializeDialogData(): void { this.selectedUsers.set([]); - const { components, resourceName } = this.config.data || {}; + const { components, resourceName, allowAddingContributorsFromParentProject } = this.config.data || {}; if (components) { this.components.set(components); @@ -138,16 +147,20 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy { if (resourceName) { this.resourceName.set(resourceName); } + + if (allowAddingContributorsFromParentProject) { + this.allowAddingContributorsFromParentProject.set(allowAddingContributorsFromParentProject); + } } - private closeDialogWithData(): void { + private closeDialogWithData(AddContributorTypeValue = AddContributorType.Registered): void { const childNodeIds = this.components() .filter((c) => c.checked && !c.isCurrent) .map((c) => c.id); this.dialogRef.close({ data: this.selectedUsers(), - type: AddContributorType.Registered, + type: AddContributorTypeValue, childNodeIds: childNodeIds.length > 0 ? childNodeIds : undefined, } as ContributorDialogAddModel); } diff --git a/src/app/shared/enums/contributors/add-contributor-type.enum.ts b/src/app/shared/enums/contributors/add-contributor-type.enum.ts index 17ce058fc..cd0e360e4 100644 --- a/src/app/shared/enums/contributors/add-contributor-type.enum.ts +++ b/src/app/shared/enums/contributors/add-contributor-type.enum.ts @@ -1,4 +1,5 @@ export enum AddContributorType { Registered = 1, Unregistered, + ParentProject, } diff --git a/src/app/shared/services/contributors.service.ts b/src/app/shared/services/contributors.service.ts index 62c178759..e9a01369e 100644 --- a/src/app/shared/services/contributors.service.ts +++ b/src/app/shared/services/contributors.service.ts @@ -150,6 +150,12 @@ export class ContributorsService { .pipe(map((contributor) => ContributorsMapper.getContributor(contributor.data))); } + addContributorsFromProject(resourceType: ResourceType, resourceId: string): Observable { + const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/?copy_contributors_from_parent_project=${true}`; + let contributorData = { data: { type: AddContributorType.ParentProject } }; + return this.jsonApiService.patch(baseUrl, contributorData); + } + deleteContributor(resourceType: ResourceType, resourceId: string, userId: string): Observable { const baseUrl = `${this.getBaseUrl(resourceType, resourceId)}/${userId}/`; diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts index 4eaaead98..2ee6ccf7a 100644 --- a/src/app/shared/stores/contributors/contributors.actions.ts +++ b/src/app/shared/stores/contributors/contributors.actions.ts @@ -62,6 +62,15 @@ export class BulkAddContributors { ) {} } +export class BulkAddContributorsFromParentProject { + static readonly type = '[Contributors] Bulk Add Contributors From Parent Project'; + + constructor( + public resourceId: string | undefined | null, + public resourceType: ResourceType | undefined + ) {} +} + export class DeleteContributor { static readonly type = '[Contributors] Delete Contributor'; diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts index 1731cf44d..9d8f7fd99 100644 --- a/src/app/shared/stores/contributors/contributors.state.ts +++ b/src/app/shared/stores/contributors/contributors.state.ts @@ -11,6 +11,7 @@ import { AcceptRequestAccess, AddContributor, BulkAddContributors, + BulkAddContributorsFromParentProject, BulkUpdateContributors, ClearUsers, DeleteContributor, @@ -191,6 +192,29 @@ export class ContributorsState { ); } + @Action(BulkAddContributorsFromParentProject) + bulkAddContributorsFromParentProject( + ctx: StateContext, + action: BulkAddContributorsFromParentProject + ) { + const state = ctx.getState(); + + if (!action.resourceId || !action.resourceType) { + return; + } + + ctx.patchState({ + contributorsList: { ...state.contributorsList, isLoading: true, error: null }, + }); + + return this.contributorsService.addContributorsFromProject(action.resourceType, action.resourceId).pipe( + tap(() => { + ctx.dispatch(new GetAllContributors(action.resourceId, action.resourceType)); + }), + catchError((error) => handleSectionError(ctx, 'contributorsList', error)) + ); + } + @Action(DeleteContributor) deleteContributor(ctx: StateContext, action: DeleteContributor) { const state = ctx.getState(); diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index e29e85ef0..90b45540d 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -596,7 +596,8 @@ "bibliographicContributor": "Bibliographic Contributor", "addUnregisteredContributor": "Add Unregistered Contributor", "addRegisteredContributor": "Add Registered Contributor", - "unregisteredContributorNotification": "We will notify the user that they have been added to your project" + "unregisteredContributorNotification": "We will notify the user that they have been added to your project", + "addingContributorsFromParentProject": "Import contributors from {{projectName}}" }, "removeDialog": { "title": "Remove contributor",