diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts
index 35d007b66..bbef5fbd3 100644
--- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts
+++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.spec.ts
@@ -9,8 +9,8 @@ import { ActivatedRoute } from '@angular/router';
import { UserSelectors } from '@core/store/user';
import { ResourceType } from '@osf/shared/enums';
import { CustomConfirmationService, CustomDialogService, ToastService } from '@osf/shared/services';
-import { ContributorsSelectors } from '@osf/shared/stores';
-import { ContributorsTableComponent } from '@shared/components/contributors';
+import { ContributorsSelectors } from '@osf/shared/stores/contributors/contributors.selectors';
+import { ContributorsTableComponent } from '@shared/components/contributors/contributors-table/contributors-table.component';
import { RegistriesContributorsComponent } from './registries-contributors.component';
@@ -77,15 +77,12 @@ describe('RegistriesContributorsComponent', () => {
deleteContributor: jest.fn().mockReturnValue(of({})),
bulkUpdateContributors: jest.fn().mockReturnValue(of({})),
bulkAddContributors: jest.fn().mockReturnValue(of({})),
+ resetContributorsState: jest.fn().mockRejectedValue(of({})),
} as any;
Object.defineProperty(component, 'actions', { value: mockActions });
fixture.detectChanges();
});
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
it('should request contributors on init', () => {
const actions = (component as any).actions;
expect(actions.getContributors).toHaveBeenCalledWith('draft-1', ResourceType.DraftRegistration);
diff --git a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts
index bf07f0974..b7fdc2449 100644
--- a/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts
+++ b/src/app/features/registries/components/registries-metadata-step/registries-contributors/registries-contributors.component.ts
@@ -4,7 +4,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { Card } from 'primeng/card';
-import { TableModule, TablePageEvent } from 'primeng/table';
+import { TableModule } from 'primeng/table';
import { filter, map, of } from 'rxjs';
@@ -16,6 +16,7 @@ import {
effect,
inject,
input,
+ OnDestroy,
OnInit,
signal,
} from '@angular/core';
@@ -40,6 +41,8 @@ import {
ContributorsSelectors,
DeleteContributor,
GetAllContributors,
+ LoadMoreContributors,
+ ResetContributorsState,
} from '@osf/shared/stores';
@Component({
@@ -49,7 +52,7 @@ import {
styleUrl: './registries-contributors.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class RegistriesContributorsComponent implements OnInit {
+export class RegistriesContributorsComponent implements OnInit, OnDestroy {
control = input.required
();
readonly destroyRef = inject(DestroyRef);
@@ -65,14 +68,15 @@ export class RegistriesContributorsComponent implements OnInit {
isContributorsLoading = select(ContributorsSelectors.isContributorsLoading);
contributorsTotalCount = select(ContributorsSelectors.getContributorsTotalCount);
- page = select(ContributorsSelectors.getContributorsPageNumber);
+ isLoadingMore = select(ContributorsSelectors.isContributorsLoadingMore);
pageSize = select(ContributorsSelectors.getContributorsPageSize);
readonly tableParams = computed(() => ({
...DEFAULT_TABLE_PARAMS,
totalRecords: this.contributorsTotalCount(),
- paginator: this.contributorsTotalCount() > DEFAULT_TABLE_PARAMS.rows,
- firstRowIndex: (this.page() - 1) * this.pageSize(),
+ paginator: false,
+ scrollable: true,
+ firstRowIndex: 0,
rows: this.pageSize(),
}));
@@ -82,6 +86,8 @@ export class RegistriesContributorsComponent implements OnInit {
bulkUpdateContributors: BulkUpdateContributors,
bulkAddContributors: BulkAddContributors,
addContributor: AddContributor,
+ loadMoreContributors: LoadMoreContributors,
+ resetContributorsState: ResetContributorsState,
});
get hasChanges(): boolean {
@@ -98,6 +104,10 @@ export class RegistriesContributorsComponent implements OnInit {
this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration);
}
+ ngOnDestroy(): void {
+ this.actions.resetContributorsState();
+ }
+
onFocusOut() {
if (this.control()) {
this.control().markAsTouched();
@@ -122,13 +132,10 @@ export class RegistriesContributorsComponent implements OnInit {
}
openAddContributorDialog() {
- const addedContributorIds = this.initialContributors().map((x) => x.userId);
-
this.customDialogService
.open(AddContributorDialogComponent, {
header: 'project.contributors.addDialog.addRegisteredContributor',
width: '448px',
- data: addedContributorIds,
})
.onClose.pipe(
filter((res: ContributorDialogAddModel) => !!res),
@@ -192,10 +199,7 @@ export class RegistriesContributorsComponent implements OnInit {
});
}
- pageChanged(event: TablePageEvent) {
- const page = Math.floor(event.first / event.rows) + 1;
- const pageSize = event.rows;
-
- this.actions.getContributors(this.draftId(), ResourceType.DraftRegistration, page, pageSize);
+ loadMoreContributors(): void {
+ this.actions.loadMoreContributors(this.draftId(), ResourceType.DraftRegistration);
}
}
diff --git a/src/app/features/registries/components/review/review.component.html b/src/app/features/registries/components/review/review.component.html
index 5f7c247bf..de6695ca7 100644
--- a/src/app/features/registries/components/review/review.component.html
+++ b/src/app/features/registries/components/review/review.component.html
@@ -28,7 +28,12 @@ {{ 'common.labels.description' | translate }}
{{ 'common.labels.contributors' | translate }}
-
+
diff --git a/src/app/features/registries/components/review/review.component.ts b/src/app/features/registries/components/review/review.component.ts
index cc8b2e29a..b0ff45a0d 100644
--- a/src/app/features/registries/components/review/review.component.ts
+++ b/src/app/features/registries/components/review/review.component.ts
@@ -10,7 +10,7 @@ import { Tag } from 'primeng/tag';
import { map, of } from 'rxjs';
-import { ChangeDetectionStrategy, Component, computed, effect, inject } from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, effect, inject, OnDestroy } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute, Router } from '@angular/router';
@@ -24,6 +24,8 @@ import {
ContributorsSelectors,
FetchSelectedSubjects,
GetAllContributors,
+ LoadMoreContributors,
+ ResetContributorsState,
SubjectsSelectors,
} from '@osf/shared/stores';
@@ -58,7 +60,7 @@ import { SelectComponentsDialogComponent } from '../select-components-dialog/sel
styleUrl: './review.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
-export class ReviewComponent {
+export class ReviewComponent implements OnDestroy {
private readonly router = inject(Router);
private readonly route = inject(ActivatedRoute);
private readonly customConfirmationService = inject(CustomConfirmationService);
@@ -73,6 +75,8 @@ export class ReviewComponent {
readonly stepsData = select(RegistriesSelectors.getStepsData);
readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES;
readonly contributors = select(ContributorsSelectors.getContributors);
+ readonly areContributorsLoading = select(ContributorsSelectors.isContributorsLoading);
+ readonly hasMoreContributors = select(ContributorsSelectors.hasMoreContributors);
readonly subjects = select(SubjectsSelectors.getSelectedSubjects);
readonly components = select(RegistriesSelectors.getRegistrationComponents);
readonly license = select(RegistriesSelectors.getRegistrationLicense);
@@ -88,6 +92,8 @@ export class ReviewComponent {
getProjectsComponents: FetchProjectChildren,
fetchLicenses: FetchLicenses,
updateStepState: UpdateStepState,
+ loadMoreContributors: LoadMoreContributors,
+ resetContributorsState: ResetContributorsState,
});
private readonly draftId = toSignal(this.route.params.pipe(map((params) => params['id'])) ?? of(undefined));
@@ -134,6 +140,10 @@ export class ReviewComponent {
});
}
+ ngOnDestroy(): void {
+ this.actions.resetContributorsState();
+ }
+
goBack(): void {
const previousStep = this.pages().length;
this.router.navigate(['../', previousStep], { relativeTo: this.route });
@@ -206,4 +216,8 @@ export class ReviewComponent {
}
});
}
+
+ loadMoreContributors(): void {
+ this.actions.loadMoreContributors(this.draftId(), ResourceType.DraftRegistration);
+ }
}
diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.html b/src/app/features/registry/pages/registry-overview/registry-overview.component.html
index 55714a2c2..0a0151a42 100644
--- a/src/app/features/registry/pages/registry-overview/registry-overview.component.html
+++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.html
@@ -136,6 +136,10 @@
{{ section.title }}
(customCitationUpdated)="onCustomCitationUpdated($event)"
[canEdit]="hasWriteAccess()"
[showEditButton]="hasWriteAccess()"
+ [bibliographicContributors]="bibliographicContributors()"
+ [isBibliographicContributorsLoading]="isBibliographicContributorsLoading()"
+ [hasMoreBibliographicContributors]="hasMoreBibliographicContributors()"
+ (loadMoreContributors)="handleLoadMoreContributors()"
/>
diff --git a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts
index af1b77fb6..7df422947 100644
--- a/src/app/features/registry/pages/registry-overview/registry-overview.component.ts
+++ b/src/app/features/registry/pages/registry-overview/registry-overview.component.ts
@@ -35,7 +35,14 @@ import { hasViewOnlyParam, toCamelCase } from '@osf/shared/helpers';
import { MapRegistryOverview } from '@osf/shared/mappers';
import { SchemaResponse, ToolbarResource } from '@osf/shared/models';
import { CustomDialogService, ToastService } from '@osf/shared/services';
-import { FetchSelectedSubjects, GetBookmarksCollectionId, SubjectsSelectors } from '@osf/shared/stores';
+import {
+ ContributorsSelectors,
+ FetchSelectedSubjects,
+ GetBibliographicContributors,
+ GetBookmarksCollectionId,
+ LoadMoreBibliographicContributors,
+ SubjectsSelectors,
+} from '@osf/shared/stores';
import { ArchivingMessageComponent, RegistryRevisionsComponent, RegistryStatusesComponent } from '../../components';
import { RegistryMakeDecisionComponent } from '../../components/registry-make-decision/registry-make-decision.component';
@@ -90,6 +97,9 @@ export class RegistryOverviewComponent {
readonly areReviewActionsLoading = select(RegistryOverviewSelectors.areReviewActionsLoading);
readonly currentRevision = select(RegistriesSelectors.getSchemaResponse);
readonly isSchemaResponseLoading = select(RegistriesSelectors.getSchemaResponseLoading);
+ bibliographicContributors = select(ContributorsSelectors.getBibliographicContributors);
+ isBibliographicContributorsLoading = select(ContributorsSelectors.isBibliographicContributorsLoading);
+ hasMoreBibliographicContributors = select(ContributorsSelectors.hasMoreBibliographicContributors);
readonly hasWriteAccess = select(RegistryOverviewSelectors.hasWriteAccess);
readonly hasAdminAccess = select(RegistryOverviewSelectors.hasAdminAccess);
@@ -180,6 +190,8 @@ export class RegistryOverviewComponent {
getRegistryReviewActions: GetRegistryReviewActions,
getSchemaResponse: FetchAllSchemaResponses,
createSchemaResponse: CreateSchemaResponse,
+ getBibliographicContributors: GetBibliographicContributors,
+ loadMoreBibliographicContributors: LoadMoreBibliographicContributors,
});
revisionId: string | null = null;
@@ -213,6 +225,7 @@ export class RegistryOverviewComponent {
effect(() => {
if (this.registryId()) {
this.actions.getRegistryById(this.registryId());
+ this.actions.getBibliographicContributors(this.registryId(), ResourceType.Registration);
}
});
@@ -272,6 +285,10 @@ export class RegistryOverviewComponent {
.subscribe();
}
+ handleLoadMoreContributors(): void {
+ this.actions.loadMoreBibliographicContributors(this.registry()?.id, ResourceType.Registration);
+ }
+
private navigateToJustificationPage(): void {
const revisionId = this.revisionId || this.revisionInProgress?.id;
this.router.navigate([`/registries/revisions/${revisionId}/justification`]);
diff --git a/src/app/features/registry/registry.routes.ts b/src/app/features/registry/registry.routes.ts
index f26006bbe..e4161724b 100644
--- a/src/app/features/registry/registry.routes.ts
+++ b/src/app/features/registry/registry.routes.ts
@@ -5,13 +5,7 @@ import { Routes } from '@angular/router';
import { viewOnlyGuard } from '@osf/core/guards';
import { ResourceType } from '@osf/shared/enums';
import { LicensesService } from '@osf/shared/services';
-import {
- CitationsState,
- ContributorsState,
- DuplicatesState,
- SubjectsState,
- ViewOnlyLinkState,
-} from '@osf/shared/stores';
+import { CitationsState, DuplicatesState, SubjectsState, ViewOnlyLinkState } from '@osf/shared/stores';
import { ActivityLogsState } from '@shared/stores/activity-logs';
import { AnalyticsState } from '../analytics/store';
@@ -52,7 +46,7 @@ export const registryRoutes: Routes = [
{
path: 'metadata',
loadChildren: () => import('@osf/features/metadata/metadata.routes').then((mod) => mod.metadataRoutes),
- providers: [provideStates([SubjectsState, ContributorsState])],
+ providers: [provideStates([SubjectsState])],
data: { resourceType: ResourceType.Registration },
canActivate: [viewOnlyGuard],
},
@@ -68,7 +62,7 @@ export const registryRoutes: Routes = [
canActivate: [viewOnlyGuard],
loadComponent: () => import('../contributors/contributors.component').then((mod) => mod.ContributorsComponent),
data: { resourceType: ResourceType.Registration },
- providers: [provideStates([ContributorsState, ViewOnlyLinkState])],
+ providers: [provideStates([ViewOnlyLinkState])],
},
{
path: 'analytics',
diff --git a/src/app/shared/components/contributors-list/contributors-list.component.html b/src/app/shared/components/contributors-list/contributors-list.component.html
index 7c379d360..814ad1dac 100644
--- a/src/app/shared/components/contributors-list/contributors-list.component.html
+++ b/src/app/shared/components/contributors-list/contributors-list.component.html
@@ -1,17 +1,34 @@
-
+
@if (anonymous()) {
{{ 'project.overview.metadata.anonymousContributors' | translate }}
} @else {
- @for (contributor of contributors(); track contributor.id) {
-
- @if (readonly() || contributor.isUnregisteredContributor || !contributor.id || contributor.deactivated) {
-
{{ contributor.fullName }}{{ $last ? '' : ',' }}
- } @else {
-
- {{ contributor.fullName }}{{ $last ? '' : ',' }}
-
- }
+ @if (isLoading()) {
+
+ } @else {
+ @for (contributor of contributors(); track contributor.id) {
+
+ }
}
}
+
+@if (hasLoadMore()) {
+
+}
diff --git a/src/app/shared/components/contributors-list/contributors-list.component.scss b/src/app/shared/components/contributors-list/contributors-list.component.scss
index e69de29bb..b9bc65ea4 100644
--- a/src/app/shared/components/contributors-list/contributors-list.component.scss
+++ b/src/app/shared/components/contributors-list/contributors-list.component.scss
@@ -0,0 +1,3 @@
+:host {
+ width: 100%;
+}
diff --git a/src/app/shared/components/contributors-list/contributors-list.component.ts b/src/app/shared/components/contributors-list/contributors-list.component.ts
index 89cc6ab67..b84335abc 100644
--- a/src/app/shared/components/contributors-list/contributors-list.component.ts
+++ b/src/app/shared/components/contributors-list/contributors-list.component.ts
@@ -1,19 +1,26 @@
import { TranslatePipe } from '@ngx-translate/core';
-import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { Button } from 'primeng/button';
+import { Skeleton } from 'primeng/skeleton';
+
+import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ContributorModel } from '@shared/models';
@Component({
selector: 'osf-contributors-list',
- imports: [RouterLink, TranslatePipe],
+ imports: [RouterLink, TranslatePipe, Skeleton, Button],
templateUrl: './contributors-list.component.html',
styleUrl: './contributors-list.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ContributorsListComponent {
contributors = input.required
[]>();
+ isLoading = input(false);
+ hasLoadMore = input(false);
readonly = input(false);
anonymous = input(false);
+
+ loadMoreContributors = output();
}
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 528e586bc..b235be0d0 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
@@ -11,7 +11,12 @@
} @else {
@for (item of users(); track $index) {
}
diff --git a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts
index 240492015..cc4fb7387 100644
--- a/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts
+++ b/src/app/shared/components/contributors/add-contributor-dialog/add-contributor-dialog.component.ts
@@ -14,6 +14,7 @@ import {
Component,
computed,
DestroyRef,
+ effect,
inject,
OnDestroy,
OnInit,
@@ -84,6 +85,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
readonly hasComponents = computed(() => this.components().length > 0);
readonly buttonLabel = computed(() => (this.isComponentsState() ? 'common.buttons.done' : 'common.buttons.next'));
+ constructor() {
+ this.setupEffects();
+ }
+
ngOnInit(): void {
this.initializeDialogData();
this.setSearchSubscription();
@@ -160,8 +165,10 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
.filter((c) => c.checked && !c.isCurrent)
.map((c) => c.id);
+ const filteredUsers = this.selectedUsers().filter((user) => !user.disabled);
+
this.dialogRef.close({
- data: this.selectedUsers(),
+ data: filteredUsers,
type: AddContributorTypeValue,
childNodeIds: childNodeIds.length > 0 ? childNodeIds : undefined,
} as ContributorDialogAddModel);
@@ -189,4 +196,18 @@ export class AddContributorDialogComponent implements OnInit, OnDestroy {
this.currentPage.set(1);
this.first.set(0);
}
+
+ private setupEffects(): void {
+ effect(() => {
+ const usersList = this.users();
+
+ if (usersList.length > 0) {
+ const checkedUsers = usersList.filter((user) => user.checked);
+
+ if (checkedUsers.length > 0) {
+ this.selectedUsers.set(checkedUsers);
+ }
+ }
+ });
+ }
}
diff --git a/src/app/shared/components/contributors/contributors-table/contributors-table.component.html b/src/app/shared/components/contributors/contributors-table/contributors-table.component.html
index 99e579a57..9d8f37d3f 100644
--- a/src/app/shared/components/contributors/contributors-table/contributors-table.component.html
+++ b/src/app/shared/components/contributors/contributors-table/contributors-table.component.html
@@ -1,17 +1,9 @@
@@ -182,6 +174,22 @@
}
+
+ @if (showLoadMore() && index === contributors().length - 1) {
+
+ |
+
+ |
+
+ }
diff --git a/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts b/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts
index 467b1b31a..d9776cbbb 100644
--- a/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts
+++ b/src/app/shared/components/contributors/contributors-table/contributors-table.component.ts
@@ -3,7 +3,7 @@ import { TranslatePipe } from '@ngx-translate/core';
import { Button } from 'primeng/button';
import { Checkbox } from 'primeng/checkbox';
import { Skeleton } from 'primeng/skeleton';
-import { TableModule, TablePageEvent } from 'primeng/table';
+import { TableModule } from 'primeng/table';
import { Tooltip } from 'primeng/tooltip';
import { ChangeDetectionStrategy, Component, computed, inject, input, model, output } from '@angular/core';
@@ -41,6 +41,7 @@ import { InfoIconComponent } from '../../info-icon/info-icon.component';
export class ContributorsTableComponent {
contributors = model([]);
isLoading = input(false);
+ isLoadingMore = input(false);
tableParams = input.required();
showCurator = input(false);
showEducation = input(true);
@@ -52,7 +53,7 @@ export class ContributorsTableComponent {
hasAdminAccess = input(true);
remove = output();
- pageChanged = output();
+ loadMore = output();
customDialogService = inject(CustomDialogService);
@@ -65,6 +66,12 @@ export class ContributorsTableComponent {
deactivatedContributors = computed(() => this.contributors().some((contributor) => contributor.deactivated));
+ showLoadMore = computed(() => {
+ const currentLoadedItems = this.contributors().length;
+ const totalRecords = this.tableParams().totalRecords;
+ return currentLoadedItems > 0 && currentLoadedItems < totalRecords;
+ });
+
removeContributor(contributor: ContributorModel) {
this.remove.emit(contributor);
}
@@ -91,7 +98,7 @@ export class ContributorsTableComponent {
this.contributors.set(reorderedContributors);
}
- onPageChange(event: TablePageEvent): void {
- this.pageChanged.emit(event);
+ loadMoreItems() {
+ this.loadMore.emit();
}
}
diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.html b/src/app/shared/components/resource-metadata/resource-metadata.component.html
index cd4398182..060edb646 100644
--- a/src/app/shared/components/resource-metadata/resource-metadata.component.html
+++ b/src/app/shared/components/resource-metadata/resource-metadata.component.html
@@ -19,8 +19,11 @@ {{ 'common.labels.contributors' | translate }}
diff --git a/src/app/shared/components/resource-metadata/resource-metadata.component.ts b/src/app/shared/components/resource-metadata/resource-metadata.component.ts
index f1ef25b3a..b83423090 100644
--- a/src/app/shared/components/resource-metadata/resource-metadata.component.ts
+++ b/src/app/shared/components/resource-metadata/resource-metadata.component.ts
@@ -10,7 +10,7 @@ import { Router, RouterLink } from '@angular/router';
import { ENVIRONMENT } from '@core/provider/environment.provider';
import { OverviewCollectionsComponent } from '@osf/features/project/overview/components/overview-collections/overview-collections.component';
import { CurrentResourceType } from '@osf/shared/enums';
-import { ResourceOverview } from '@shared/models';
+import { ContributorModel, ResourceOverview } from '@shared/models';
import { AffiliatedInstitutionsViewComponent } from '../affiliated-institutions-view/affiliated-institutions-view.component';
import { ContributorsListComponent } from '../contributors-list/contributors-list.component';
@@ -44,6 +44,10 @@ export class ResourceMetadataComponent {
isCollectionsRoute = input
(false);
canEdit = input.required();
showEditButton = input();
+ bibliographicContributors = input([]);
+ isBibliographicContributorsLoading = input(false);
+ hasMoreBibliographicContributors = input(false);
+ loadMoreContributors = output();
readonly resourceTypes = CurrentResourceType;
readonly dateFormat = 'MMM d, y, h:mm a';
diff --git a/src/app/shared/components/truncated-text/truncated-text.component.scss b/src/app/shared/components/truncated-text/truncated-text.component.scss
index bb473e7e9..8c983db9c 100644
--- a/src/app/shared/components/truncated-text/truncated-text.component.scss
+++ b/src/app/shared/components/truncated-text/truncated-text.component.scss
@@ -4,7 +4,6 @@
text-overflow: ellipsis;
display: -webkit-box;
line-clamp: var(--line-clamp);
- line-height: 1.7;
-webkit-line-clamp: var(--line-clamp);
-webkit-box-orient: vertical;
white-space: pre-line;
diff --git a/src/app/shared/mappers/collections/collections.mapper.ts b/src/app/shared/mappers/collections/collections.mapper.ts
index 7a741e2d0..22969da39 100644
--- a/src/app/shared/mappers/collections/collections.mapper.ts
+++ b/src/app/shared/mappers/collections/collections.mapper.ts
@@ -37,12 +37,14 @@ export class CollectionsMapper {
facebookAppId: response.attributes.facebook_app_id,
allowSubmissions: response.attributes.allow_submissions,
allowCommenting: response.attributes.allow_commenting,
- assets: {
- style: response.attributes.assets.style,
- squareColorTransparent: response.attributes.assets.square_color_transparent,
- squareColorNoTransparent: response.attributes.assets.square_color_no_transparent,
- favicon: response.attributes.assets.favicon,
- },
+ assets: response.attributes.assets
+ ? {
+ style: response.attributes.assets.style,
+ squareColorTransparent: response.attributes.assets.square_color_transparent,
+ squareColorNoTransparent: response.attributes.assets.square_color_no_transparent,
+ favicon: response.attributes.assets.favicon,
+ }
+ : {},
shareSource: response.attributes.share_source,
sharePublishType: response.attributes.share_publish_type,
permissions: response.attributes.permissions,
diff --git a/src/app/shared/mappers/resource-overview.mappers.ts b/src/app/shared/mappers/resource-overview.mappers.ts
index 419e515fa..cb4a69622 100644
--- a/src/app/shared/mappers/resource-overview.mappers.ts
+++ b/src/app/shared/mappers/resource-overview.mappers.ts
@@ -1,12 +1,13 @@
import { ProjectOverview } from '@osf/features/project/overview/models';
import { RegistryOverview } from '@osf/features/registry/models';
-import { Institution, ResourceOverview, SubjectModel } from '../models';
+import { ContributorModel, Institution, ResourceOverview, SubjectModel } from '../models';
export function MapProjectOverview(
project: ProjectOverview,
subjects: SubjectModel[],
- isAnonymous = false
+ isAnonymous = false,
+ bibliographicContributors: ContributorModel[] = []
): ResourceOverview {
return {
id: project.id,
@@ -35,7 +36,7 @@ export function MapProjectOverview(
currentUserIsContributorOrGroupMember: project.currentUserIsContributorOrGroupMember,
wikiEnabled: project.wikiEnabled,
subjects: subjects,
- contributors: project.contributors?.filter(Boolean) || [],
+ contributors: bibliographicContributors?.filter(Boolean) || [],
customCitation: project.customCitation || null,
region: project.region || undefined,
affiliatedInstitutions: project.affiliatedInstitutions?.filter(Boolean) || undefined,
diff --git a/src/app/shared/mappers/view-only-links.mapper.ts b/src/app/shared/mappers/view-only-links.mapper.ts
index a168611b2..3d56a1c4d 100644
--- a/src/app/shared/mappers/view-only-links.mapper.ts
+++ b/src/app/shared/mappers/view-only-links.mapper.ts
@@ -24,7 +24,7 @@ export class ViewOnlyLinksMapper {
id: creator?.id || '',
fullName: creator?.fullName || '',
},
- nodes: item.embeds.nodes.data.map(
+ nodes: item.embeds?.nodes?.data?.map(
(node) =>
({
id: node.id,
@@ -59,7 +59,7 @@ export class ViewOnlyLinksMapper {
id: creator?.id || '',
fullName: creator?.fullName || '',
},
- nodes: item.embeds.nodes.data.map(
+ nodes: item.embeds?.nodes?.data?.map(
(node) =>
({
id: node.id,
diff --git a/src/app/shared/models/contributors/contributor-add.model.ts b/src/app/shared/models/contributors/contributor-add.model.ts
index 31bc3bf35..9e6a6f6c6 100644
--- a/src/app/shared/models/contributors/contributor-add.model.ts
+++ b/src/app/shared/models/contributors/contributor-add.model.ts
@@ -5,4 +5,6 @@ export interface ContributorAddModel {
fullName?: string;
email?: string;
index?: number;
+ checked?: boolean;
+ disabled?: boolean;
}
diff --git a/src/app/shared/stores/contributors/contributors.actions.ts b/src/app/shared/stores/contributors/contributors.actions.ts
index 2ee6ccf7a..e2654c6c9 100644
--- a/src/app/shared/stores/contributors/contributors.actions.ts
+++ b/src/app/shared/stores/contributors/contributors.actions.ts
@@ -128,3 +128,32 @@ export class RejectRequestAccess {
public resourceType: ResourceType | undefined
) {}
}
+
+export class GetBibliographicContributors {
+ static readonly type = '[Contributors] Get Bibliographic Contributors';
+
+ constructor(
+ public resourceId: string | undefined | null,
+ public resourceType: ResourceType | undefined,
+ public page = 1,
+ public pageSize = DEFAULT_TABLE_PARAMS.rows
+ ) {}
+}
+
+export class LoadMoreBibliographicContributors {
+ static readonly type = '[Contributors] Load More Bibliographic Contributors';
+
+ constructor(
+ public resourceId: string | undefined | null,
+ public resourceType: ResourceType | undefined
+ ) {}
+}
+
+export class LoadMoreContributors {
+ static readonly type = '[Contributors] Load More Contributors';
+
+ constructor(
+ public resourceId: string | undefined | null,
+ public resourceType: ResourceType | undefined
+ ) {}
+}
diff --git a/src/app/shared/stores/contributors/contributors.model.ts b/src/app/shared/stores/contributors/contributors.model.ts
index 5f7c92b61..89d34a7fd 100644
--- a/src/app/shared/stores/contributors/contributors.model.ts
+++ b/src/app/shared/stores/contributors/contributors.model.ts
@@ -2,16 +2,21 @@ import { DEFAULT_TABLE_PARAMS } from '@osf/shared/constants';
import { ContributorAddModel, ContributorModel, RequestAccessModel } from '@osf/shared/models';
import { AsyncStateModel, AsyncStateWithTotalCount } from '@osf/shared/models/store';
-export interface ContributorsListModel extends AsyncStateWithTotalCount {
+export interface ContributorsList extends AsyncStateWithTotalCount {
+ page: number;
+ pageSize: number;
+}
+
+export interface ContributorsListWithFiltersModel extends ContributorsList {
searchValue: string | null;
permissionFilter: string | null;
bibliographyFilter: boolean | null;
- page: number;
- pageSize: number;
+ isLoadingMore: boolean;
}
export interface ContributorsStateModel {
- contributorsList: ContributorsListModel;
+ contributorsList: ContributorsListWithFiltersModel;
+ bibliographicContributorsList: ContributorsList;
requestAccessList: AsyncStateModel;
users: AsyncStateWithTotalCount;
}
@@ -27,6 +32,16 @@ export const CONTRIBUTORS_STATE_DEFAULTS: ContributorsStateModel = {
totalCount: 0,
page: 1,
pageSize: DEFAULT_TABLE_PARAMS.rows,
+ isLoadingMore: false,
+ },
+ bibliographicContributorsList: {
+ data: [],
+ isLoading: false,
+ isSubmitting: false,
+ error: null,
+ totalCount: 0,
+ page: 0,
+ pageSize: DEFAULT_TABLE_PARAMS.rows,
},
requestAccessList: {
data: [],
diff --git a/src/app/shared/stores/contributors/contributors.selectors.ts b/src/app/shared/stores/contributors/contributors.selectors.ts
index f3816a2de..57f026482 100644
--- a/src/app/shared/stores/contributors/contributors.selectors.ts
+++ b/src/app/shared/stores/contributors/contributors.selectors.ts
@@ -30,11 +30,29 @@ export class ContributorsSelectors {
@Selector([ContributorsState])
static getBibliographicContributors(state: ContributorsStateModel) {
- if (!state?.contributorsList?.data) {
+ if (!state?.bibliographicContributorsList?.data) {
return [];
}
- return state.contributorsList.data.filter((contributor) => contributor.isBibliographic);
+ return state.bibliographicContributorsList.data;
+ }
+
+ @Selector([ContributorsState])
+ static isBibliographicContributorsLoading(state: ContributorsStateModel) {
+ return state?.bibliographicContributorsList?.isLoading || false;
+ }
+
+ @Selector([ContributorsState])
+ static getBibliographicContributorsTotalCount(state: ContributorsStateModel) {
+ return state?.bibliographicContributorsList?.totalCount || 0;
+ }
+
+ @Selector([ContributorsState])
+ static hasMoreBibliographicContributors(state: ContributorsStateModel) {
+ return (
+ state?.bibliographicContributorsList?.data?.length < state?.bibliographicContributorsList?.totalCount &&
+ !state?.bibliographicContributorsList?.isLoading
+ );
}
@Selector([ContributorsState])
@@ -43,8 +61,8 @@ export class ContributorsSelectors {
}
@Selector([ContributorsState])
- static getContributorsPageNumber(state: ContributorsStateModel) {
- return state.contributorsList.page;
+ static isContributorsLoadingMore(state: ContributorsStateModel) {
+ return state?.contributorsList?.isLoadingMore || false;
}
@Selector([ContributorsState])
@@ -57,6 +75,13 @@ export class ContributorsSelectors {
return state.contributorsList.totalCount;
}
+ @Selector([ContributorsState])
+ static hasMoreContributors(state: ContributorsStateModel) {
+ return (
+ state?.contributorsList?.data?.length < state?.contributorsList?.totalCount && !state?.contributorsList?.isLoading
+ );
+ }
+
@Selector([ContributorsState])
static getUsers(state: ContributorsStateModel) {
return state?.users?.data || [];
diff --git a/src/app/shared/stores/contributors/contributors.state.ts b/src/app/shared/stores/contributors/contributors.state.ts
index 091cdf130..cd894866a 100644
--- a/src/app/shared/stores/contributors/contributors.state.ts
+++ b/src/app/shared/stores/contributors/contributors.state.ts
@@ -11,12 +11,15 @@ import {
AcceptRequestAccess,
AddContributor,
BulkAddContributors,
- BulkUpdateContributors,
BulkAddContributorsFromParentProject,
+ BulkUpdateContributors,
ClearUsers,
DeleteContributor,
GetAllContributors,
+ GetBibliographicContributors,
GetRequestAccessContributors,
+ LoadMoreBibliographicContributors,
+ LoadMoreContributors,
RejectRequestAccess,
ResetContributorsState,
SearchUsers,
@@ -49,19 +52,23 @@ export class ContributorsState {
ctx.patchState({
contributorsList: {
...state.contributorsList,
- data: [],
- isLoading: true,
+ data: page === 1 ? [] : state.contributorsList.data,
+ isLoading: page === 1,
+ isLoadingMore: page > 1,
error: null,
},
});
return this.contributorsService.getAllContributors(action.resourceType, action.resourceId, page, pageSize).pipe(
tap((res) => {
+ const data = page === 1 ? res.data : [...state.contributorsList.data, ...res.data];
+
ctx.patchState({
contributorsList: {
...state.contributorsList,
- data: res.data,
+ data,
isLoading: false,
+ isLoadingMore: false,
totalCount: res.totalCount,
page,
pageSize,
@@ -283,17 +290,21 @@ export class ContributorsState {
users: { ...state.users, isLoading: true, error: null },
});
- const addedContributorsIds = state.contributorsList.data.map((contributor) => contributor.userId);
-
if (!action.searchValue) {
return of([]);
}
return this.contributorsService.searchUsers(action.searchValue, action.page).pipe(
tap((users) => {
+ const addedContributorsIds = state.contributorsList.data.map((contributor) => contributor.userId);
+
ctx.patchState({
users: {
- data: users.data.filter((user) => !addedContributorsIds.includes(user.id!)),
+ data: users.data.map((user) => ({
+ ...user,
+ checked: addedContributorsIds.includes(user.id!),
+ disabled: addedContributorsIds.includes(user.id!),
+ })),
isLoading: false,
error: '',
totalCount: users.totalCount,
@@ -309,6 +320,67 @@ export class ContributorsState {
ctx.patchState({ users: { data: [], isLoading: false, error: null, totalCount: 0 } });
}
+ @Action(GetBibliographicContributors)
+ getBibliographicContributors(ctx: StateContext, action: GetBibliographicContributors) {
+ const state = ctx.getState();
+
+ if (!action.resourceId || !action.resourceType) {
+ return;
+ }
+
+ ctx.patchState({
+ bibliographicContributorsList: {
+ ...state.bibliographicContributorsList,
+ data: action.page === 1 ? [] : state.bibliographicContributorsList.data,
+ isLoading: true,
+ error: null,
+ },
+ });
+
+ return this.contributorsService
+ .getBibliographicContributors(action.resourceType, action.resourceId, action.page, action.pageSize)
+ .pipe(
+ tap((res) => {
+ const data = action.page === 1 ? res.data : [...state.bibliographicContributorsList.data, ...res.data];
+
+ ctx.patchState({
+ bibliographicContributorsList: {
+ data,
+ isLoading: false,
+ error: null,
+ page: action.page,
+ pageSize: res.pageSize,
+ totalCount: res.totalCount,
+ },
+ });
+ }),
+ catchError((error) => handleSectionError(ctx, 'bibliographicContributorsList', error))
+ );
+ }
+
+ @Action(LoadMoreBibliographicContributors)
+ loadMoreBibliographicContributors(
+ ctx: StateContext,
+ action: LoadMoreBibliographicContributors
+ ) {
+ const state = ctx.getState();
+ const nextPage = state.bibliographicContributorsList.page + 1;
+ const nextPageSize = state.bibliographicContributorsList.pageSize;
+
+ return ctx.dispatch(
+ new GetBibliographicContributors(action.resourceId, action.resourceType, nextPage, nextPageSize)
+ );
+ }
+
+ @Action(LoadMoreContributors)
+ loadMoreContributors(ctx: StateContext, action: LoadMoreContributors) {
+ const state = ctx.getState();
+ const nextPage = state.contributorsList.page + 1;
+ const nextPageSize = state.contributorsList.pageSize;
+
+ return ctx.dispatch(new GetAllContributors(action.resourceId, action.resourceType, nextPage, nextPageSize));
+ }
+
@Action(ResetContributorsState)
resetState(ctx: StateContext) {
ctx.setState({ ...CONTRIBUTORS_STATE_DEFAULTS });
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 7bedfa43c..01f68a556 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -57,7 +57,8 @@
"removeAll": "Remove All",
"accept": "Accept",
"reject": "Reject",
- "loadMore": "Load more"
+ "loadMore": "Load more",
+ "seeMore": "See more"
},
"accessibility": {
"help": "Help",
diff --git a/src/styles/overrides/table.scss b/src/styles/overrides/table.scss
index a27d956b4..416cee613 100644
--- a/src/styles/overrides/table.scss
+++ b/src/styles/overrides/table.scss
@@ -85,10 +85,13 @@ p-table {
td,
th {
- background-color: transparent;
border-bottom: 1px solid var(--grey-2);
}
+ td {
+ background-color: transparent;
+ }
+
tr {
&:hover {
background: transparent;