From 18d44e79e8fbc18eeaaa45ca0434ba2c5b53d4b6 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 11:18:54 +0300 Subject: [PATCH 01/42] refactor(preprints-state): Renamed Preprints -> PreprintProviders --- .../preprints/constants/preprints.routes.ts | 4 +- .../landing/preprints-landing.component.ts | 14 +++---- .../preprint-provider-discover.component.ts | 6 +-- .../preprint-provider-overview.component.ts | 12 +++--- .../select-preprint-service.component.ts | 9 ++-- .../submit-preprint-stepper.component.ts | 6 +-- .../store/preprint-providers/index.ts | 4 ++ .../preprint-providers.actions.ts} | 0 .../preprint-providers.model.ts} | 2 +- .../preprint-providers.selectors.ts | 41 +++++++++++++++++++ .../preprint-providers.state.ts} | 26 +++++++----- .../preprints/store/preprints/index.ts | 4 -- .../store/preprints/preprints.selectors.ts | 41 ------------------- 13 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 src/app/features/preprints/store/preprint-providers/index.ts rename src/app/features/preprints/store/{preprints/preprints.actions.ts => preprint-providers/preprint-providers.actions.ts} (100%) rename src/app/features/preprints/store/{preprints/preprints.model.ts => preprint-providers/preprint-providers.model.ts} (90%) create mode 100644 src/app/features/preprints/store/preprint-providers/preprint-providers.selectors.ts rename src/app/features/preprints/store/{preprints/preprints.state.ts => preprint-providers/preprint-providers.state.ts} (87%) delete mode 100644 src/app/features/preprints/store/preprints/index.ts delete mode 100644 src/app/features/preprints/store/preprints/preprints.selectors.ts diff --git a/src/app/features/preprints/constants/preprints.routes.ts b/src/app/features/preprints/constants/preprints.routes.ts index 3d039e468..f1ac2a629 100644 --- a/src/app/features/preprints/constants/preprints.routes.ts +++ b/src/app/features/preprints/constants/preprints.routes.ts @@ -3,7 +3,7 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { PreprintsComponent } from '@osf/features/preprints/preprints.component'; -import { PreprintsState } from '@osf/features/preprints/store/preprints'; +import { PreprintProvidersState } from '@osf/features/preprints/store/preprint-providers'; import { PreprintsDiscoverState } from '@osf/features/preprints/store/preprints-discover'; import { PreprintsResourcesFiltersState } from '@osf/features/preprints/store/preprints-resources-filters'; import { PreprintsResourcesFiltersOptionsState } from '@osf/features/preprints/store/preprints-resources-filters-options'; @@ -16,7 +16,7 @@ export const preprintsRoutes: Routes = [ component: PreprintsComponent, providers: [ provideStates([ - PreprintsState, + PreprintProvidersState, PreprintsDiscoverState, PreprintsResourcesFiltersState, PreprintsResourcesFiltersOptionsState, diff --git a/src/app/features/preprints/pages/landing/preprints-landing.component.ts b/src/app/features/preprints/pages/landing/preprints-landing.component.ts index 0990c38b5..d8f4c060a 100644 --- a/src/app/features/preprints/pages/landing/preprints-landing.component.ts +++ b/src/app/features/preprints/pages/landing/preprints-landing.component.ts @@ -19,8 +19,8 @@ import { GetHighlightedSubjectsByProviderId, GetPreprintProviderById, GetPreprintProvidersToAdvertise, - PreprintsSelectors, -} from '@osf/features/preprints/store/preprints'; + PreprintProvidersSelectors, +} from '@osf/features/preprints/store/preprint-providers'; import { SearchInputComponent } from '@shared/components'; import { ResourceTab } from '@shared/enums'; import { BrandService } from '@shared/services'; @@ -55,11 +55,11 @@ export class PreprintsLandingComponent implements OnInit, OnDestroy { getHighlightedSubjectsByProviderId: GetHighlightedSubjectsByProviderId, }); - osfPreprintProvider = select(PreprintsSelectors.getPreprintProviderDetails(this.OSF_PROVIDER_ID)); - isPreprintProviderLoading = select(PreprintsSelectors.isPreprintProviderDetailsLoading); - preprintProvidersToAdvertise = select(PreprintsSelectors.getPreprintProvidersToAdvertise); - highlightedSubjectsByProviderId = select(PreprintsSelectors.getHighlightedSubjectsForProvider); - areSubjectsLoading = select(PreprintsSelectors.areSubjectsLoading); + osfPreprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.OSF_PROVIDER_ID)); + isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); + preprintProvidersToAdvertise = select(PreprintProvidersSelectors.getPreprintProvidersToAdvertise); + highlightedSubjectsByProviderId = select(PreprintProvidersSelectors.getHighlightedSubjectsForProvider); + areSubjectsLoading = select(PreprintProvidersSelectors.areSubjectsLoading); constructor() { effect(() => { diff --git a/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts b/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts index f1425fa2c..a35e60a4d 100644 --- a/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts +++ b/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts @@ -19,7 +19,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { PreprintsResourcesComponent } from '@osf/features/preprints/components'; import { PreprintProviderHeroComponent } from '@osf/features/preprints/components/preprint-provider-hero/preprint-provider-hero.component'; -import { GetPreprintProviderById, PreprintsSelectors } from '@osf/features/preprints/store/preprints'; +import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { GetResources, ResetState, @@ -79,8 +79,8 @@ export class PreprintProviderDiscoverComponent implements OnInit, OnDestroy { searchControl = new FormControl(''); - preprintProvider = select(PreprintsSelectors.getPreprintProviderDetails(this.providerId())); - isPreprintProviderLoading = select(PreprintsSelectors.isPreprintProviderDetailsLoading); + preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); + isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); creatorSelected = select(PreprintsResourcesFiltersSelectors.getCreator); dateCreatedSelected = select(PreprintsResourcesFiltersSelectors.getDateCreated); diff --git a/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts b/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts index 402bdd514..336a96c32 100644 --- a/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts +++ b/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts @@ -12,8 +12,8 @@ import { PreprintProviderHeroComponent } from '@osf/features/preprints/component import { GetHighlightedSubjectsByProviderId, GetPreprintProviderById, - PreprintsSelectors, -} from '@osf/features/preprints/store/preprints'; + PreprintProvidersSelectors, +} from '@osf/features/preprints/store/preprint-providers'; import { BrandService } from '@shared/services'; import { BrowserTabHelper, HeaderStyleHelper } from '@shared/utils'; @@ -38,11 +38,11 @@ export class PreprintProviderOverviewComponent implements OnInit, OnDestroy { getPreprintProviderById: GetPreprintProviderById, getHighlightedSubjectsByProviderId: GetHighlightedSubjectsByProviderId, }); - preprintProvider = select(PreprintsSelectors.getPreprintProviderDetails(this.providerId())); - isPreprintProviderLoading = select(PreprintsSelectors.isPreprintProviderDetailsLoading); + preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); + isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - highlightedSubjectsByProviderId = select(PreprintsSelectors.getHighlightedSubjectsForProvider); - areSubjectsLoading = select(PreprintsSelectors.areSubjectsLoading); + highlightedSubjectsByProviderId = select(PreprintProvidersSelectors.getHighlightedSubjectsForProvider); + areSubjectsLoading = select(PreprintProvidersSelectors.areSubjectsLoading); constructor() { effect(() => { diff --git a/src/app/features/preprints/pages/select-preprint-service/select-preprint-service.component.ts b/src/app/features/preprints/pages/select-preprint-service/select-preprint-service.component.ts index 7ca811f7f..453415ee4 100644 --- a/src/app/features/preprints/pages/select-preprint-service/select-preprint-service.component.ts +++ b/src/app/features/preprints/pages/select-preprint-service/select-preprint-service.component.ts @@ -12,7 +12,10 @@ import { ChangeDetectionStrategy, Component, HostBinding, OnInit } from '@angula import { RouterLink } from '@angular/router'; import { PreprintProviderShortInfo } from '@osf/features/preprints/models'; -import { GetPreprintProvidersAllowingSubmissions, PreprintsSelectors } from '@osf/features/preprints/store/preprints'; +import { + GetPreprintProvidersAllowingSubmissions, + PreprintProvidersSelectors, +} from '@osf/features/preprints/store/preprint-providers'; import { SetSelectedPreprintProviderId, SubmitPreprintSelectors } from '@osf/features/preprints/store/submit-preprint'; import { SubHeaderComponent } from '@shared/components'; import { DecodeHtmlPipe } from '@shared/pipes'; @@ -32,8 +35,8 @@ export class SelectPreprintServiceComponent implements OnInit { setSelectedPreprintProviderId: SetSelectedPreprintProviderId, }); - preprintProvidersAllowingSubmissions = select(PreprintsSelectors.getPreprintProvidersAllowingSubmissions); - areProvidersLoading = select(PreprintsSelectors.arePreprintProvidersAllowingSubmissionsLoading); + preprintProvidersAllowingSubmissions = select(PreprintProvidersSelectors.getPreprintProvidersAllowingSubmissions); + areProvidersLoading = select(PreprintProvidersSelectors.arePreprintProvidersAllowingSubmissionsLoading); selectedProviderId = select(SubmitPreprintSelectors.getSelectedProviderId); skeletonArray = Array.from({ length: 8 }, (_, i) => i + 1); diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index fca7477e8..7c8322e52 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -24,7 +24,7 @@ import { } from '@osf/features/preprints/components'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; import { SubmitSteps } from '@osf/features/preprints/enums'; -import { GetPreprintProviderById, PreprintsSelectors } from '@osf/features/preprints/store/preprints'; +import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { ResetStateAndDeletePreprint, SetSelectedPreprintProviderId, @@ -56,8 +56,8 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { readonly SubmitStepsEnum = SubmitSteps; readonly submitPreprintSteps = submitPreprintSteps; - preprintProvider = select(PreprintsSelectors.getPreprintProviderDetails(this.providerId())); - isPreprintProviderLoading = select(PreprintsSelectors.isPreprintProviderDetailsLoading); + preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); + isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); currentStep = signal(0); isWeb = toSignal(inject(IS_WEB)); diff --git a/src/app/features/preprints/store/preprint-providers/index.ts b/src/app/features/preprints/store/preprint-providers/index.ts new file mode 100644 index 000000000..73ee4f8ed --- /dev/null +++ b/src/app/features/preprints/store/preprint-providers/index.ts @@ -0,0 +1,4 @@ +export * from './preprint-providers.actions'; +export * from './preprint-providers.model'; +export * from './preprint-providers.selectors'; +export * from './preprint-providers.state'; diff --git a/src/app/features/preprints/store/preprints/preprints.actions.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts similarity index 100% rename from src/app/features/preprints/store/preprints/preprints.actions.ts rename to src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts diff --git a/src/app/features/preprints/store/preprints/preprints.model.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts similarity index 90% rename from src/app/features/preprints/store/preprints/preprints.model.ts rename to src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts index d9bc5680d..205e7ee9e 100644 --- a/src/app/features/preprints/store/preprints/preprints.model.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts @@ -1,7 +1,7 @@ import { PreprintProviderDetails, PreprintProviderShortInfo, Subject } from '@osf/features/preprints/models'; import { AsyncStateModel } from '@shared/models'; -export interface PreprintsStateModel { +export interface PreprintProvidersStateModel { preprintProvidersDetails: AsyncStateModel; preprintProvidersToAdvertise: AsyncStateModel; preprintProvidersAllowingSubmissions: AsyncStateModel; diff --git a/src/app/features/preprints/store/preprint-providers/preprint-providers.selectors.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.selectors.ts new file mode 100644 index 000000000..55918d6b9 --- /dev/null +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.selectors.ts @@ -0,0 +1,41 @@ +import { Selector } from '@ngxs/store'; + +import { PreprintProvidersState, PreprintProvidersStateModel } from '@osf/features/preprints/store/preprint-providers'; + +export class PreprintProvidersSelectors { + @Selector([PreprintProvidersState]) + static getPreprintProviderDetails(providerId: string) { + return (state: { preprintProviders: PreprintProvidersStateModel }) => + state.preprintProviders.preprintProvidersDetails.data.find((provider) => provider.id.includes(providerId)); + } + + @Selector([PreprintProvidersState]) + static isPreprintProviderDetailsLoading(state: PreprintProvidersStateModel) { + return state.preprintProvidersDetails.isLoading; + } + + @Selector([PreprintProvidersState]) + static getPreprintProvidersToAdvertise(state: PreprintProvidersStateModel) { + return state.preprintProvidersToAdvertise.data; + } + + @Selector([PreprintProvidersState]) + static getPreprintProvidersAllowingSubmissions(state: PreprintProvidersStateModel) { + return state.preprintProvidersAllowingSubmissions.data; + } + + @Selector([PreprintProvidersState]) + static arePreprintProvidersAllowingSubmissionsLoading(state: PreprintProvidersStateModel) { + return state.preprintProvidersAllowingSubmissions.isLoading; + } + + @Selector([PreprintProvidersState]) + static getHighlightedSubjectsForProvider(state: PreprintProvidersStateModel) { + return state.highlightedSubjectsForProvider.data; + } + + @Selector([PreprintProvidersState]) + static areSubjectsLoading(state: PreprintProvidersStateModel) { + return state.highlightedSubjectsForProvider.isLoading; + } +} diff --git a/src/app/features/preprints/store/preprints/preprints.state.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts similarity index 87% rename from src/app/features/preprints/store/preprints/preprints.state.ts rename to src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts index d7a0b237a..670efc00d 100644 --- a/src/app/features/preprints/store/preprints/preprints.state.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts @@ -7,17 +7,17 @@ import { catchError } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; import { PreprintsService } from '@osf/features/preprints/services'; + import { GetHighlightedSubjectsByProviderId, GetPreprintProviderById, GetPreprintProvidersAllowingSubmissions, GetPreprintProvidersToAdvertise, -} from '@osf/features/preprints/store/preprints/preprints.actions'; - -import { PreprintsStateModel } from './'; +} from './preprint-providers.actions'; +import { PreprintProvidersStateModel } from './preprint-providers.model'; -@State({ - name: 'preprints', +@State({ + name: 'preprintProviders', defaults: { preprintProvidersDetails: { data: [], @@ -42,12 +42,12 @@ import { PreprintsStateModel } from './'; }, }) @Injectable() -export class PreprintsState { +export class PreprintProvidersState { #preprintsService = inject(PreprintsService); private readonly REFRESH_INTERVAL = 5 * 60 * 1000; @Action(GetPreprintProviderById) - getPreprintProviderById(ctx: StateContext, action: GetPreprintProviderById) { + getPreprintProviderById(ctx: StateContext, action: GetPreprintProviderById) { const state = ctx.getState(); const cachedData = state.preprintProvidersDetails.data.find((p) => p.id === action.id); const shouldRefresh = this.shouldRefresh(cachedData?.lastFetched); @@ -79,7 +79,7 @@ export class PreprintsState { } @Action(GetPreprintProvidersToAdvertise) - getPreprintProvidersToAdvertise(ctx: StateContext) { + getPreprintProvidersToAdvertise(ctx: StateContext) { ctx.setState(patch({ preprintProvidersToAdvertise: patch({ isLoading: true }) })); return this.#preprintsService.getPreprintProvidersToAdvertise().pipe( @@ -98,7 +98,7 @@ export class PreprintsState { } @Action(GetPreprintProvidersAllowingSubmissions) - getPreprintProvidersAllowingSubmissions(ctx: StateContext) { + getPreprintProvidersAllowingSubmissions(ctx: StateContext) { ctx.setState(patch({ preprintProvidersAllowingSubmissions: patch({ isLoading: true }) })); return this.#preprintsService.getPreprintProvidersAllowingSubmissions().pipe( @@ -118,7 +118,7 @@ export class PreprintsState { @Action(GetHighlightedSubjectsByProviderId) getHighlightedSubjectsByProviderId( - ctx: StateContext, + ctx: StateContext, action: GetHighlightedSubjectsByProviderId ) { ctx.setState(patch({ highlightedSubjectsForProvider: patch({ isLoading: true }) })); @@ -146,7 +146,11 @@ export class PreprintsState { return Date.now() - lastFetched > this.REFRESH_INTERVAL; } - private handleError(ctx: StateContext, section: keyof PreprintsStateModel, error: Error) { + private handleError( + ctx: StateContext, + section: keyof PreprintProvidersStateModel, + error: Error + ) { ctx.patchState({ [section]: { ...ctx.getState()[section], diff --git a/src/app/features/preprints/store/preprints/index.ts b/src/app/features/preprints/store/preprints/index.ts deleted file mode 100644 index d1f1d4f1a..000000000 --- a/src/app/features/preprints/store/preprints/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './preprints.actions'; -export * from './preprints.model'; -export * from './preprints.selectors'; -export * from './preprints.state'; diff --git a/src/app/features/preprints/store/preprints/preprints.selectors.ts b/src/app/features/preprints/store/preprints/preprints.selectors.ts deleted file mode 100644 index 7c3ac8de3..000000000 --- a/src/app/features/preprints/store/preprints/preprints.selectors.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Selector } from '@ngxs/store'; - -import { PreprintsState, PreprintsStateModel } from '@osf/features/preprints/store/preprints'; - -export class PreprintsSelectors { - @Selector([PreprintsState]) - static getPreprintProviderDetails(providerId: string) { - return (state: { preprints: PreprintsStateModel }) => - state.preprints.preprintProvidersDetails.data.find((provider) => provider.id.includes(providerId)); - } - - @Selector([PreprintsState]) - static isPreprintProviderDetailsLoading(state: PreprintsStateModel) { - return state.preprintProvidersDetails.isLoading; - } - - @Selector([PreprintsState]) - static getPreprintProvidersToAdvertise(state: PreprintsStateModel) { - return state.preprintProvidersToAdvertise.data; - } - - @Selector([PreprintsState]) - static getPreprintProvidersAllowingSubmissions(state: PreprintsStateModel) { - return state.preprintProvidersAllowingSubmissions.data; - } - - @Selector([PreprintsState]) - static arePreprintProvidersAllowingSubmissionsLoading(state: PreprintsStateModel) { - return state.preprintProvidersAllowingSubmissions.isLoading; - } - - @Selector([PreprintsState]) - static getHighlightedSubjectsForProvider(state: PreprintsStateModel) { - return state.highlightedSubjectsForProvider.data; - } - - @Selector([PreprintsState]) - static areSubjectsLoading(state: PreprintsStateModel) { - return state.highlightedSubjectsForProvider.isLoading; - } -} From 187896ccde054e3cc4830ea61bcee0a603c78235 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 11:38:03 +0300 Subject: [PATCH 02/42] refactor(preprints-services): Renamed services to have 'preprints' prefix. Decomposed --- src/app/features/preprints/mappers/index.ts | 1 + .../mappers/preprint-providers.mapper.ts | 56 +++++++ .../preprints/mappers/preprints.mapper.ts | 58 +------- src/app/features/preprints/services/index.ts | 5 +- ...ce.ts => preprint-contributors.service.ts} | 2 +- .../services/preprint-files.service.ts | 96 ++++++++++++ ...ervice.ts => preprint-licenses.service.ts} | 2 +- .../services/preprint-providers.service.ts | 72 +++++++++ .../preprints/services/preprints.service.ts | 139 +----------------- .../preprint-providers.state.ts | 12 +- .../submit-preprint/submit-preprint.state.ts | 23 +-- 11 files changed, 255 insertions(+), 211 deletions(-) create mode 100644 src/app/features/preprints/mappers/preprint-providers.mapper.ts rename src/app/features/preprints/services/{contributors.service.ts => preprint-contributors.service.ts} (98%) create mode 100644 src/app/features/preprints/services/preprint-files.service.ts rename src/app/features/preprints/services/{licenses.service.ts => preprint-licenses.service.ts} (98%) create mode 100644 src/app/features/preprints/services/preprint-providers.service.ts diff --git a/src/app/features/preprints/mappers/index.ts b/src/app/features/preprints/mappers/index.ts index 7ed240bce..05b261f6b 100644 --- a/src/app/features/preprints/mappers/index.ts +++ b/src/app/features/preprints/mappers/index.ts @@ -1 +1,2 @@ +export * from './preprint-providers.mapper'; export * from './preprints.mapper'; diff --git a/src/app/features/preprints/mappers/preprint-providers.mapper.ts b/src/app/features/preprints/mappers/preprint-providers.mapper.ts new file mode 100644 index 000000000..3f046ce91 --- /dev/null +++ b/src/app/features/preprints/mappers/preprint-providers.mapper.ts @@ -0,0 +1,56 @@ +import { + PreprintProviderDetails, + PreprintProviderDetailsGetResponse, + PreprintProviderShortInfo, + Subject, + SubjectGetResponse, +} from '@osf/features/preprints/models'; + +export class PreprintProvidersMapper { + static fromPreprintProviderDetailsGetResponse(response: PreprintProviderDetailsGetResponse): PreprintProviderDetails { + const brandRaw = response.embeds!.brand.data; + return { + id: response.id, + name: response.attributes.name, + descriptionHtml: response.attributes.description, + advisoryBoardHtml: response.attributes.advisory_board, + examplePreprintId: response.attributes.example, + domain: response.attributes.domain, + footerLinksHtml: response.attributes.footer_links, + preprintWord: response.attributes.preprint_word, + allowSubmissions: response.attributes.allow_submissions, + brand: { + id: brandRaw.id, + name: brandRaw.attributes.name, + heroLogoImageUrl: brandRaw.attributes.hero_logo_image, + heroBackgroundImageUrl: brandRaw.attributes.hero_background_image, + topNavLogoImageUrl: brandRaw.attributes.topnav_logo_image, + primaryColor: brandRaw.attributes.primary_color, + secondaryColor: brandRaw.attributes.secondary_color, + }, + iri: response.links.iri, + faviconUrl: response.attributes.assets.favicon, + }; + } + + static toPreprintProviderShortInfoFromGetResponse( + response: PreprintProviderDetailsGetResponse[] + ): PreprintProviderShortInfo[] { + return response.map((item) => ({ + id: item.id, + descriptionHtml: item.attributes.description, + name: item.attributes.name, + whiteWideImageUrl: item.attributes.assets?.wide_white, + squareColorNoTransparentImageUrl: item.attributes.assets?.square_color_no_transparent, + })); + } + + static fromSubjectsGetResponse(providerId: string, response: SubjectGetResponse[]): Subject[] { + return response.map((subject) => ({ + id: subject.id, + text: subject.attributes.text, + taxonomy_name: subject.attributes.taxonomy_name, + preprintProviderId: providerId, + })); + } +} diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 25e3a5cd1..84d3770af 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -1,63 +1,7 @@ import { ApiData } from '@core/models'; -import { - Preprint, - PreprintJsonApi, - PreprintProviderDetails, - PreprintProviderDetailsGetResponse, - PreprintProviderShortInfo, - PreprintsRelationshipsJsonApi, - Subject, - SubjectGetResponse, -} from '@osf/features/preprints/models'; +import { Preprint, PreprintJsonApi, PreprintsRelationshipsJsonApi } from '@osf/features/preprints/models'; export class PreprintsMapper { - static fromPreprintProviderDetailsGetResponse(response: PreprintProviderDetailsGetResponse): PreprintProviderDetails { - const brandRaw = response.embeds!.brand.data; - return { - id: response.id, - name: response.attributes.name, - descriptionHtml: response.attributes.description, - advisoryBoardHtml: response.attributes.advisory_board, - examplePreprintId: response.attributes.example, - domain: response.attributes.domain, - footerLinksHtml: response.attributes.footer_links, - preprintWord: response.attributes.preprint_word, - allowSubmissions: response.attributes.allow_submissions, - brand: { - id: brandRaw.id, - name: brandRaw.attributes.name, - heroLogoImageUrl: brandRaw.attributes.hero_logo_image, - heroBackgroundImageUrl: brandRaw.attributes.hero_background_image, - topNavLogoImageUrl: brandRaw.attributes.topnav_logo_image, - primaryColor: brandRaw.attributes.primary_color, - secondaryColor: brandRaw.attributes.secondary_color, - }, - iri: response.links.iri, - faviconUrl: response.attributes.assets.favicon, - }; - } - - static toPreprintProviderShortInfoFromGetResponse( - response: PreprintProviderDetailsGetResponse[] - ): PreprintProviderShortInfo[] { - return response.map((item) => ({ - id: item.id, - descriptionHtml: item.attributes.description, - name: item.attributes.name, - whiteWideImageUrl: item.attributes.assets?.wide_white, - squareColorNoTransparentImageUrl: item.attributes.assets?.square_color_no_transparent, - })); - } - - static fromSubjectsGetResponse(providerId: string, response: SubjectGetResponse[]): Subject[] { - return response.map((subject) => ({ - id: subject.id, - text: subject.attributes.text, - taxonomy_name: subject.attributes.taxonomy_name, - preprintProviderId: providerId, - })); - } - static toCreatePayload(title: string, abstract: string, providerId: string) { return { data: { diff --git a/src/app/features/preprints/services/index.ts b/src/app/features/preprints/services/index.ts index a88975c9e..339550d00 100644 --- a/src/app/features/preprints/services/index.ts +++ b/src/app/features/preprints/services/index.ts @@ -1,3 +1,6 @@ -export { ContributorsService } from './contributors.service'; +export { PreprintContributorsService } from './preprint-contributors.service'; +export { PreprintFilesService } from './preprint-files.service'; +export { PreprintLicensesService } from './preprint-licenses.service'; +export { PreprintProvidersService } from './preprint-providers.service'; export { PreprintsService } from './preprints.service'; export { PreprintsFiltersOptionsService } from './preprints-resource-filters.service'; diff --git a/src/app/features/preprints/services/contributors.service.ts b/src/app/features/preprints/services/preprint-contributors.service.ts similarity index 98% rename from src/app/features/preprints/services/contributors.service.ts rename to src/app/features/preprints/services/preprint-contributors.service.ts index 049393205..9fa26f3cd 100644 --- a/src/app/features/preprints/services/contributors.service.ts +++ b/src/app/features/preprints/services/preprint-contributors.service.ts @@ -13,7 +13,7 @@ import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root', }) -export class ContributorsService { +export class PreprintContributorsService { private jsonApiService = inject(JsonApiService); private apiUrl = environment.apiUrl; diff --git a/src/app/features/preprints/services/preprint-files.service.ts b/src/app/features/preprints/services/preprint-files.service.ts new file mode 100644 index 000000000..141d255b8 --- /dev/null +++ b/src/app/features/preprints/services/preprint-files.service.ts @@ -0,0 +1,96 @@ +import { map, Observable, switchMap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { Primitive, StringOrNull } from '@core/helpers'; +import { JsonApiService } from '@core/services'; +import { ApiData, JsonApiResponse } from '@osf/core/models'; +import { PreprintsMapper } from '@osf/features/preprints/mappers'; +import { + Preprint, + PreprintFilesLinks, + PreprintJsonApi, + PreprintsRelationshipsJsonApi, +} from '@osf/features/preprints/models'; +import { GetFileResponse, GetFilesResponse, IdName, NodeData, OsfFile } from '@osf/shared/models'; +import { FilesService } from '@shared/services'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class PreprintFilesService { + private filesService = inject(FilesService); + private jsonApiService = inject(JsonApiService); + + updateFileRelationship(preprintId: string, fileId: string): Observable { + return this.jsonApiService + .patch>( + `${environment.apiUrl}/preprints/${preprintId}/`, + { + data: { + type: 'preprints', + id: preprintId, + attributes: {}, + relationships: { + primary_file: { + data: { + type: 'files', + id: fileId, + }, + }, + }, + }, + } + ) + .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response))); + } + + getPreprintFilesLinks(id: string): Observable { + return this.jsonApiService.get(`${environment.apiUrl}/preprints/${id}/files/`).pipe( + map((response) => { + const rel = response.data[0].relationships; + const links = response.data[0].links; + + return { + filesLink: rel.files.links.related.href, + uploadFileLink: links.upload, + }; + }) + ); + } + + getAvailableProjects(searchTerm: StringOrNull): Observable { + const params: Record = {}; + params['page'] = 1; + if (searchTerm) { + params['filter[title]'] = searchTerm; + } + + return this.jsonApiService + .get>(`${environment.apiUrl}/users/me/nodes/`, params) + .pipe( + map((response) => { + return response.data.map((item) => ({ + id: item.id, + name: item.attributes.title, + })); + }) + ); + } + + getProjectFiles(projectId: string): Observable { + return this.jsonApiService.get(`${environment.apiUrl}/nodes/${projectId}/files/`).pipe( + switchMap((response: GetFilesResponse) => { + return this.jsonApiService + .get>(response.data[0].relationships.root_folder.links.related.href) + .pipe( + switchMap((fileResponse) => + this.filesService.getFilesWithoutFiltering(fileResponse.data.relationships.files.links.related.href) + ) + ); + }) + ); + } +} diff --git a/src/app/features/preprints/services/licenses.service.ts b/src/app/features/preprints/services/preprint-licenses.service.ts similarity index 98% rename from src/app/features/preprints/services/licenses.service.ts rename to src/app/features/preprints/services/preprint-licenses.service.ts index 06b62098c..b0438b7f5 100644 --- a/src/app/features/preprints/services/licenses.service.ts +++ b/src/app/features/preprints/services/preprint-licenses.service.ts @@ -18,7 +18,7 @@ import { environment } from 'src/environments/environment'; @Injectable({ providedIn: 'root', }) -export class LicensesService { +export class PreprintLicensesService { private apiUrl = environment.apiUrl; private readonly jsonApiService = inject(JsonApiService); diff --git a/src/app/features/preprints/services/preprint-providers.service.ts b/src/app/features/preprints/services/preprint-providers.service.ts new file mode 100644 index 000000000..d6342885d --- /dev/null +++ b/src/app/features/preprints/services/preprint-providers.service.ts @@ -0,0 +1,72 @@ +import { map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiResponse } from '@core/models'; +import { JsonApiService } from '@core/services'; +import { PreprintProvidersMapper } from '@osf/features/preprints/mappers'; +import { + PreprintProviderDetails, + PreprintProviderDetailsGetResponse, + PreprintProviderShortInfo, + Subject, + SubjectGetResponse, +} from '@osf/features/preprints/models'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class PreprintProvidersService { + private jsonApiService = inject(JsonApiService); + private baseUrl = `${environment.apiUrl}/providers/preprints/`; + + getPreprintProviderById(id: string): Observable { + return this.jsonApiService + .get>(`${this.baseUrl}${id}/?embed=brand`) + .pipe( + map((response) => { + return PreprintProvidersMapper.fromPreprintProviderDetailsGetResponse(response.data); + }) + ); + } + + getPreprintProvidersToAdvertise(): Observable { + return this.jsonApiService + .get< + JsonApiResponse + >(`${this.baseUrl}?filter[advertise_on_discover_page]=true&reload=true`) + .pipe( + map((response) => { + return PreprintProvidersMapper.toPreprintProviderShortInfoFromGetResponse( + response.data.filter((item) => !item.id.includes('osf')) + ); + }) + ); + } + + getPreprintProvidersAllowingSubmissions(): Observable { + return this.jsonApiService + .get< + JsonApiResponse + >(`${this.baseUrl}?filter[allow_submissions]=true`) + .pipe( + map((response) => { + return PreprintProvidersMapper.toPreprintProviderShortInfoFromGetResponse(response.data); + }) + ); + } + + getHighlightedSubjectsByProviderId(providerId: string): Observable { + return this.jsonApiService + .get< + JsonApiResponse + >(`${this.baseUrl}${providerId}/subjects/highlighted/?page[size]=20`) + .pipe( + map((response) => { + return PreprintProvidersMapper.fromSubjectsGetResponse(providerId, response.data); + }) + ); + } +} diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index adc0e74b4..9e0a30141 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -1,24 +1,11 @@ -import { map, Observable, switchMap } from 'rxjs'; +import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { Primitive, StringOrNull } from '@core/helpers'; import { JsonApiService } from '@core/services'; -import { ApiData, JsonApiResponse } from '@osf/core/models'; +import { ApiData } from '@osf/core/models'; import { PreprintsMapper } from '@osf/features/preprints/mappers'; -import { - Preprint, - PreprintFilesLinks, - PreprintJsonApi, - PreprintProviderDetails, - PreprintProviderDetailsGetResponse, - PreprintProviderShortInfo, - PreprintsRelationshipsJsonApi, - Subject, - SubjectGetResponse, -} from '@osf/features/preprints/models'; -import { GetFileResponse, GetFilesResponse, IdName, NodeData, OsfFile } from '@shared/models'; -import { FilesService } from '@shared/services'; +import { Preprint, PreprintJsonApi, PreprintsRelationshipsJsonApi } from '@osf/features/preprints/models'; import { environment } from 'src/environments/environment'; @@ -26,9 +13,7 @@ import { environment } from 'src/environments/environment'; providedIn: 'root', }) export class PreprintsService { - private filesService = inject(FilesService); private jsonApiService = inject(JsonApiService); - private baseUrl = `${environment.apiUrl}/providers/preprints/`; private domainToApiFieldMap: Record = { title: 'title', @@ -39,54 +24,6 @@ export class PreprintsService { tags: 'tags', }; - getPreprintProviderById(id: string): Observable { - return this.jsonApiService - .get>(`${this.baseUrl}${id}/?embed=brand`) - .pipe( - map((response) => { - return PreprintsMapper.fromPreprintProviderDetailsGetResponse(response.data); - }) - ); - } - - getPreprintProvidersToAdvertise(): Observable { - return this.jsonApiService - .get< - JsonApiResponse - >(`${this.baseUrl}?filter[advertise_on_discover_page]=true&reload=true`) - .pipe( - map((response) => { - return PreprintsMapper.toPreprintProviderShortInfoFromGetResponse( - response.data.filter((item) => !item.id.includes('osf')) - ); - }) - ); - } - - getPreprintProvidersAllowingSubmissions(): Observable { - return this.jsonApiService - .get< - JsonApiResponse - >(`${this.baseUrl}?filter[allow_submissions]=true`) - .pipe( - map((response) => { - return PreprintsMapper.toPreprintProviderShortInfoFromGetResponse(response.data); - }) - ); - } - - getHighlightedSubjectsByProviderId(providerId: string): Observable { - return this.jsonApiService - .get< - JsonApiResponse - >(`${this.baseUrl}${providerId}/subjects/highlighted/?page[size]=20`) - .pipe( - map((response) => { - return PreprintsMapper.fromSubjectsGetResponse(providerId, response.data); - }) - ); - } - createPreprint(title: string, abstract: string, providerId: string) { const payload = PreprintsMapper.toCreatePayload(title, abstract, providerId); return this.jsonApiService @@ -121,76 +58,6 @@ export class PreprintsService { .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response))); } - updateFileRelationship(preprintId: string, fileId: string): Observable { - return this.jsonApiService - .patch>( - `${environment.apiUrl}/preprints/${preprintId}/`, - { - data: { - type: 'preprints', - id: preprintId, - attributes: {}, - relationships: { - primary_file: { - data: { - type: 'files', - id: fileId, - }, - }, - }, - }, - } - ) - .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response))); - } - - getPreprintFilesLinks(id: string): Observable { - return this.jsonApiService.get(`${environment.apiUrl}/preprints/${id}/files/`).pipe( - map((response) => { - const rel = response.data[0].relationships; - const links = response.data[0].links; - - return { - filesLink: rel.files.links.related.href, - uploadFileLink: links.upload, - }; - }) - ); - } - - getAvailableProjects(searchTerm: StringOrNull): Observable { - const params: Record = {}; - params['page'] = 1; - if (searchTerm) { - params['filter[title]'] = searchTerm; - } - - return this.jsonApiService - .get>(`${environment.apiUrl}/users/me/nodes/`, params) - .pipe( - map((response) => { - return response.data.map((item) => ({ - id: item.id, - name: item.attributes.title, - })); - }) - ); - } - - getProjectFiles(projectId: string): Observable { - return this.jsonApiService.get(`${environment.apiUrl}/nodes/${projectId}/files/`).pipe( - switchMap((response: GetFilesResponse) => { - return this.jsonApiService - .get>(response.data[0].relationships.root_folder.links.related.href) - .pipe( - switchMap((fileResponse) => - this.filesService.getFilesWithoutFiltering(fileResponse.data.relationships.files.links.related.href) - ) - ); - }) - ); - } - private mapPreprintDomainToApiPayload(domainPayload: Partial): Partial { const apiPayload: Record = {}; Object.entries(domainPayload).forEach(([key, value]) => { diff --git a/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts index 670efc00d..95bb7c6b4 100644 --- a/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.state.ts @@ -6,7 +6,7 @@ import { catchError } from 'rxjs/operators'; import { inject, Injectable } from '@angular/core'; -import { PreprintsService } from '@osf/features/preprints/services'; +import { PreprintProvidersService } from '@osf/features/preprints/services'; import { GetHighlightedSubjectsByProviderId, @@ -43,7 +43,7 @@ import { PreprintProvidersStateModel } from './preprint-providers.model'; }) @Injectable() export class PreprintProvidersState { - #preprintsService = inject(PreprintsService); + private preprintProvidersService = inject(PreprintProvidersService); private readonly REFRESH_INTERVAL = 5 * 60 * 1000; @Action(GetPreprintProviderById) @@ -58,7 +58,7 @@ export class PreprintProvidersState { ctx.setState(patch({ preprintProvidersDetails: patch({ isLoading: true }) })); - return this.#preprintsService.getPreprintProviderById(action.id).pipe( + return this.preprintProvidersService.getPreprintProviderById(action.id).pipe( tap((preprintProvider) => { const exists = state.preprintProvidersDetails.data.some((p) => p.id === preprintProvider.id); preprintProvider.lastFetched = Date.now(); @@ -82,7 +82,7 @@ export class PreprintProvidersState { getPreprintProvidersToAdvertise(ctx: StateContext) { ctx.setState(patch({ preprintProvidersToAdvertise: patch({ isLoading: true }) })); - return this.#preprintsService.getPreprintProvidersToAdvertise().pipe( + return this.preprintProvidersService.getPreprintProvidersToAdvertise().pipe( tap((data) => { ctx.setState( patch({ @@ -101,7 +101,7 @@ export class PreprintProvidersState { getPreprintProvidersAllowingSubmissions(ctx: StateContext) { ctx.setState(patch({ preprintProvidersAllowingSubmissions: patch({ isLoading: true }) })); - return this.#preprintsService.getPreprintProvidersAllowingSubmissions().pipe( + return this.preprintProvidersService.getPreprintProvidersAllowingSubmissions().pipe( tap((data) => { ctx.setState( patch({ @@ -123,7 +123,7 @@ export class PreprintProvidersState { ) { ctx.setState(patch({ highlightedSubjectsForProvider: patch({ isLoading: true }) })); - return this.#preprintsService.getHighlightedSubjectsByProviderId(action.providerId).pipe( + return this.preprintProvidersService.getHighlightedSubjectsByProviderId(action.providerId).pipe( tap((subjects) => { ctx.setState( patch({ diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 10f98cf1c..4b34b0d07 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -9,8 +9,12 @@ import { inject, Injectable } from '@angular/core'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint } from '@osf/features/preprints/models'; -import { ContributorsService, PreprintsService } from '@osf/features/preprints/services'; -import { LicensesService } from '@osf/features/preprints/services/licenses.service'; +import { + PreprintContributorsService, + PreprintFilesService, + PreprintLicensesService, + PreprintsService, +} from '@osf/features/preprints/services'; import { OsfFile } from '@shared/models'; import { FilesService } from '@shared/services'; @@ -83,9 +87,10 @@ import { @Injectable() export class SubmitPreprintState { private preprintsService = inject(PreprintsService); + private preprintFilesService = inject(PreprintFilesService); private fileService = inject(FilesService); - private contributorsService = inject(ContributorsService); - private licensesService = inject(LicensesService); + private contributorsService = inject(PreprintContributorsService); + private licensesService = inject(PreprintLicensesService); @Action(SetSelectedPreprintProviderId) setSelectedPreprintProviderId(ctx: StateContext, action: SetSelectedPreprintProviderId) { @@ -126,7 +131,7 @@ export class SubmitPreprintState { } ctx.setState(patch({ preprintFilesLinks: patch({ isLoading: true }) })); - return this.preprintsService.getPreprintFilesLinks(state.createdPreprint.data.id).pipe( + return this.preprintFilesService.getPreprintFilesLinks(state.createdPreprint.data.id).pipe( tap((preprintStorage) => { ctx.setState(patch({ preprintFilesLinks: patch({ isLoading: false, data: preprintStorage }) })); }) @@ -149,7 +154,7 @@ export class SubmitPreprintState { const createdFileId = file.id.split('/')[1]; ctx.dispatch(new GetPreprintFiles()); - return this.preprintsService.updateFileRelationship(state.createdPreprint.data!.id, createdFileId).pipe( + return this.preprintFilesService.updateFileRelationship(state.createdPreprint.data!.id, createdFileId).pipe( tap((preprint: Preprint) => { ctx.patchState({ createdPreprint: { @@ -210,7 +215,7 @@ export class SubmitPreprintState { getAvailableProjects(ctx: StateContext, action: GetAvailableProjects) { ctx.setState(patch({ availableProjects: patch({ isLoading: true }) })); - return this.preprintsService.getAvailableProjects(action.searchTerm).pipe( + return this.preprintFilesService.getAvailableProjects(action.searchTerm).pipe( tap((projects) => { ctx.setState( patch({ @@ -229,7 +234,7 @@ export class SubmitPreprintState { getProjectFiles(ctx: StateContext, action: GetProjectFiles) { ctx.setState(patch({ projectFiles: patch({ isLoading: true }) })); - return this.preprintsService.getProjectFiles(action.projectId).pipe( + return this.preprintFilesService.getProjectFiles(action.projectId).pipe( tap((files: OsfFile[]) => { ctx.setState( patch({ @@ -346,7 +351,7 @@ export class SubmitPreprintState { const fileIdAfterCopy = file.id.split('/')[1]; - return this.preprintsService.updateFileRelationship(createdPreprintId, fileIdAfterCopy).pipe( + return this.preprintFilesService.updateFileRelationship(createdPreprintId, fileIdAfterCopy).pipe( tap((preprint: Preprint) => { ctx.patchState({ createdPreprint: { From 06650247ff12814e28668de5824c82ca7dd48777 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 11:41:32 +0300 Subject: [PATCH 03/42] fix(json-api-post): Fixed errors during to merge conflict --- src/app/features/preprints/services/preprints.service.ts | 6 +++--- src/app/shared/services/files.service.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 9e0a30141..af95335f2 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -3,7 +3,7 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@core/services'; -import { ApiData } from '@osf/core/models'; +import { ApiData, JsonApiResponse } from '@osf/core/models'; import { PreprintsMapper } from '@osf/features/preprints/mappers'; import { Preprint, PreprintJsonApi, PreprintsRelationshipsJsonApi } from '@osf/features/preprints/models'; @@ -28,11 +28,11 @@ export class PreprintsService { const payload = PreprintsMapper.toCreatePayload(title, abstract, providerId); return this.jsonApiService .post< - ApiData + JsonApiResponse, null> >(`${environment.apiUrl}/preprints/`, payload) .pipe( map((response) => { - return PreprintsMapper.fromPreprintJsonApi(response); + return PreprintsMapper.fromPreprintJsonApi(response.data); }) ); } diff --git a/src/app/shared/services/files.service.ts b/src/app/shared/services/files.service.ts index fc63a2990..570418965 100644 --- a/src/app/shared/services/files.service.ts +++ b/src/app/shared/services/files.service.ts @@ -260,7 +260,9 @@ export class FilesService { resource: resourceId, }; return this.#jsonApiService - .post>(moveLink, body) - .pipe(map((response) => MapFile(response))); + .post< + JsonApiResponse, null> + >(moveLink, body) + .pipe(map((response) => MapFile(response.data))); } } From 13e082d11908d024fbf8a5ec7acd672faeda6200 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 11:43:36 +0300 Subject: [PATCH 04/42] refactor(preprints-state): Renamed Preprints -> PreprintProviders --- .../preprint-providers/preprint-providers.actions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts index 9254138a9..fce5ff94e 100644 --- a/src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.actions.ts @@ -1,19 +1,19 @@ export class GetPreprintProviderById { - static readonly type = '[Preprints] Get Provider By Id'; + static readonly type = '[Preprint Providers] Get Provider By Id'; constructor(public id: string) {} } export class GetHighlightedSubjectsByProviderId { - static readonly type = '[Preprints] Get Highlighted Subjects By Provider Id'; + static readonly type = '[Preprint Providers] Get Highlighted Subjects By Provider Id'; constructor(public providerId: string) {} } export class GetPreprintProvidersToAdvertise { - static readonly type = '[Preprints] Get Preprint Providers To Advertise'; + static readonly type = '[Preprint Providers] Get Preprint Providers To Advertise'; } export class GetPreprintProvidersAllowingSubmissions { - static readonly type = '[Preprints] Get Preprint Providers That Allows Submissions'; + static readonly type = '[Preprint Providers] Get Preprint Providers That Allows Submissions'; } From a55bd9340406cb82cbe67668b38440f2390ebac0 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 12:21:23 +0300 Subject: [PATCH 05/42] refactor(preprints-models): Decomposed, used shared models. Moved some to shared --- .../browse-by-subjects.component.html | 2 +- .../browse-by-subjects.component.ts | 8 +- src/app/features/preprints/constants/index.ts | 1 - .../mappers/preprint-providers.mapper.ts | 18 +-- src/app/features/preprints/models/index.ts | 5 +- .../models/preprint-json-api.models.ts | 44 +++++ .../preprint-provider-json-api.models.ts | 30 ++++ .../models/preprint-provider.models.ts | 26 +++ .../preprints/models/preprint.models.ts | 35 ++++ .../preprints/models/preprints.models.ts | 150 ------------------ .../services/preprint-providers.service.ts | 19 +-- .../preprint-providers.model.ts | 4 +- src/app/shared/models/brand.json-api.model.ts | 12 ++ src/app/shared/models/index.ts | 1 + .../shared/models/subject/subject.model.ts | 1 + .../models/subject/subjects-json-api.model.ts | 6 +- 16 files changed, 179 insertions(+), 183 deletions(-) create mode 100644 src/app/features/preprints/models/preprint-json-api.models.ts create mode 100644 src/app/features/preprints/models/preprint-provider-json-api.models.ts create mode 100644 src/app/features/preprints/models/preprint-provider.models.ts create mode 100644 src/app/features/preprints/models/preprint.models.ts delete mode 100644 src/app/features/preprints/models/preprints.models.ts create mode 100644 src/app/shared/models/brand.json-api.model.ts diff --git a/src/app/features/preprints/components/browse-by-subjects/browse-by-subjects.component.html b/src/app/features/preprints/components/browse-by-subjects/browse-by-subjects.component.html index 2aff25b20..6ab5364ec 100644 --- a/src/app/features/preprints/components/browse-by-subjects/browse-by-subjects.component.html +++ b/src/app/features/preprints/components/browse-by-subjects/browse-by-subjects.component.html @@ -10,7 +10,7 @@

{{ 'preprints.browseBySubjects.title' | translate }}

({ id: item.id, @@ -45,12 +44,11 @@ export class PreprintProvidersMapper { })); } - static fromSubjectsGetResponse(providerId: string, response: SubjectGetResponse[]): Subject[] { - return response.map((subject) => ({ + static fromSubjectsGetResponse(data: SubjectDataJsonApi[]): Subject[] { + return data.map((subject) => ({ id: subject.id, - text: subject.attributes.text, - taxonomy_name: subject.attributes.taxonomy_name, - preprintProviderId: providerId, + name: subject.attributes.text, + iri: subject.links.iri, })); } } diff --git a/src/app/features/preprints/models/index.ts b/src/app/features/preprints/models/index.ts index 1cf98e527..8df6fdc96 100644 --- a/src/app/features/preprints/models/index.ts +++ b/src/app/features/preprints/models/index.ts @@ -1,3 +1,6 @@ +export * from './preprint.models'; +export * from './preprint-json-api.models'; export * from './preprint-licenses-json-api.models'; -export * from './preprints.models'; +export * from './preprint-provider.models'; +export * from './preprint-provider-json-api.models'; export * from './submit-preprint-form.models'; diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts new file mode 100644 index 000000000..f826748ae --- /dev/null +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -0,0 +1,44 @@ +import { StringOrNull } from '@core/helpers'; +import { LicenseRecordJsonApi } from '@shared/models'; + +export interface PreprintJsonApi { + date_created: string; + date_modified: string; + date_published: Date | null; + original_publication_date: Date | null; + custom_publication_citation: StringOrNull; + doi: StringOrNull; + preprint_doi_created: Date | null; + title: string; + description: string; + is_published: boolean; + is_preprint_orphan: boolean; + license_record: LicenseRecordJsonApi | null; + tags: string[]; + date_withdrawn: Date | null; + current_user_permissions: string[]; + public: boolean; + reviews_state: string; + date_last_transitioned: Date | null; + version: number; + is_latest_version: boolean; + has_coi: boolean; + conflict_of_interest_statement: StringOrNull; + has_data_links: boolean; +} + +export interface PreprintsRelationshipsJsonApi { + primary_file: { + links: { + related: { + href: string; + }; + }; + }; + license: { + data: { + id: string; + type: 'licenses'; + }; + }; +} diff --git a/src/app/features/preprints/models/preprint-provider-json-api.models.ts b/src/app/features/preprints/models/preprint-provider-json-api.models.ts new file mode 100644 index 000000000..aee9d93fc --- /dev/null +++ b/src/app/features/preprints/models/preprint-provider-json-api.models.ts @@ -0,0 +1,30 @@ +import { StringOrNull } from '@core/helpers'; +import { BrandGetResponse } from '@shared/models'; + +export interface PreprintProviderDetailsJsonApi { + id: string; + type: 'preprint-providers'; + attributes: { + name: string; + description: string; + advisory_board: StringOrNull; + example: string; + domain: string; + footer_links: string; + preprint_word: string; + assets: { + wide_white: string; + square_color_no_transparent: string; + favicon: string; + }; + allow_submissions: boolean; + }; + embeds?: { + brand: { + data: BrandGetResponse; + }; + }; + links: { + iri: string; + }; +} diff --git a/src/app/features/preprints/models/preprint-provider.models.ts b/src/app/features/preprints/models/preprint-provider.models.ts new file mode 100644 index 000000000..8979fd539 --- /dev/null +++ b/src/app/features/preprints/models/preprint-provider.models.ts @@ -0,0 +1,26 @@ +import { StringOrNull } from '@core/helpers'; +import { Brand } from '@shared/models'; + +export interface PreprintProviderDetails { + id: string; + name: string; + descriptionHtml: string; + advisoryBoardHtml: StringOrNull; + examplePreprintId: string; + domain: string; + footerLinksHtml: string; + preprintWord: string; + allowSubmissions: boolean; + brand: Brand; + lastFetched?: number; + iri: string; + faviconUrl: string; +} + +export interface PreprintProviderShortInfo { + id: string; + name: string; + descriptionHtml: string; + whiteWideImageUrl: string; + squareColorNoTransparentImageUrl: string; +} diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts new file mode 100644 index 000000000..be4f0e67c --- /dev/null +++ b/src/app/features/preprints/models/preprint.models.ts @@ -0,0 +1,35 @@ +import { StringOrNull } from '@core/helpers'; +import { LicenseOptions } from '@shared/models'; + +export interface Preprint { + id: string; + dateCreated: string; + dateModified: string; + title: string; + description: string; + doi: StringOrNull; + originalPublicationDate: Date | null; + customPublicationCitation: StringOrNull; + isPublished: boolean; + tags: string[]; + isPublic: boolean; + version: number; + isLatestVersion: boolean; + primaryFileId: StringOrNull; + licenseId: StringOrNull; + licenseOptions: LicenseOptions | null; +} + +export interface PreprintFilesLinks { + filesLink: string; + uploadFileLink: string; +} + +export interface SubjectGetResponse { + id: string; + type: 'subjects'; + attributes: { + text: string; + taxonomy_name: string; + }; +} diff --git a/src/app/features/preprints/models/preprints.models.ts b/src/app/features/preprints/models/preprints.models.ts deleted file mode 100644 index c1693a546..000000000 --- a/src/app/features/preprints/models/preprints.models.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { StringOrNull } from '@core/helpers'; -import { Brand } from '@osf/shared/models'; -import { LicenseOptions, LicenseRecordJsonApi } from '@shared/models'; - -export interface PreprintProviderDetails { - id: string; - name: string; - descriptionHtml: string; - advisoryBoardHtml: StringOrNull; - examplePreprintId: string; - domain: string; - footerLinksHtml: string; - preprintWord: string; - allowSubmissions: boolean; - brand: Brand; - lastFetched?: number; - iri: string; - faviconUrl: string; -} - -export interface PreprintProviderShortInfo { - id: string; - name: string; - descriptionHtml: string; - whiteWideImageUrl: string; - squareColorNoTransparentImageUrl: string; -} - -export interface Subject { - id: string; - text: string; - taxonomy_name: string; - preprintProviderId: string; -} - -export interface Preprint { - id: string; - dateCreated: string; - dateModified: string; - title: string; - description: string; - doi: StringOrNull; - originalPublicationDate: Date | null; - customPublicationCitation: StringOrNull; - isPublished: boolean; - tags: string[]; - isPublic: boolean; - version: number; - isLatestVersion: boolean; - primaryFileId: StringOrNull; - licenseId: StringOrNull; - licenseOptions: LicenseOptions | null; -} - -export interface PreprintFilesLinks { - filesLink: string; - uploadFileLink: string; -} - -export interface PreprintProviderDetailsGetResponse { - id: string; - type: 'preprint-providers'; - attributes: { - name: string; - description: string; - advisory_board: StringOrNull; - example: string; - domain: string; - footer_links: string; - preprint_word: string; - assets: { - wide_white: string; - square_color_no_transparent: string; - favicon: string; - }; - allow_submissions: boolean; - }; - embeds?: { - brand: { - data: BrandGetResponse; - }; - }; - links: { - iri: string; - }; -} - -export interface BrandGetResponse { - id: string; - type: 'brands'; - attributes: { - name: string; - hero_logo_image: string; - hero_background_image: string; - topnav_logo_image: string; - primary_color: string; - secondary_color: string; - }; -} - -export interface SubjectGetResponse { - id: string; - type: 'subjects'; - attributes: { - text: string; - taxonomy_name: string; - }; -} - -export interface PreprintJsonApi { - date_created: string; - date_modified: string; - date_published: Date | null; - original_publication_date: Date | null; - custom_publication_citation: StringOrNull; - doi: StringOrNull; - preprint_doi_created: Date | null; - title: string; - description: string; - is_published: boolean; - is_preprint_orphan: boolean; - license_record: LicenseRecordJsonApi | null; - tags: string[]; - date_withdrawn: Date | null; - current_user_permissions: string[]; - public: boolean; - reviews_state: string; - date_last_transitioned: Date | null; - version: number; - is_latest_version: boolean; - has_coi: boolean; - conflict_of_interest_statement: StringOrNull; - has_data_links: boolean; -} - -export interface PreprintsRelationshipsJsonApi { - primary_file: { - links: { - related: { - href: string; - }; - }; - }; - license: { - data: { - id: string; - type: 'licenses'; - }; - }; -} diff --git a/src/app/features/preprints/services/preprint-providers.service.ts b/src/app/features/preprints/services/preprint-providers.service.ts index d6342885d..044e5ddfb 100644 --- a/src/app/features/preprints/services/preprint-providers.service.ts +++ b/src/app/features/preprints/services/preprint-providers.service.ts @@ -7,11 +7,10 @@ import { JsonApiService } from '@core/services'; import { PreprintProvidersMapper } from '@osf/features/preprints/mappers'; import { PreprintProviderDetails, - PreprintProviderDetailsGetResponse, + PreprintProviderDetailsJsonApi, PreprintProviderShortInfo, - Subject, - SubjectGetResponse, } from '@osf/features/preprints/models'; +import { Subject, SubjectsResponseJsonApi } from '@shared/models'; import { environment } from 'src/environments/environment'; @@ -24,7 +23,7 @@ export class PreprintProvidersService { getPreprintProviderById(id: string): Observable { return this.jsonApiService - .get>(`${this.baseUrl}${id}/?embed=brand`) + .get>(`${this.baseUrl}${id}/?embed=brand`) .pipe( map((response) => { return PreprintProvidersMapper.fromPreprintProviderDetailsGetResponse(response.data); @@ -35,7 +34,7 @@ export class PreprintProvidersService { getPreprintProvidersToAdvertise(): Observable { return this.jsonApiService .get< - JsonApiResponse + JsonApiResponse >(`${this.baseUrl}?filter[advertise_on_discover_page]=true&reload=true`) .pipe( map((response) => { @@ -48,9 +47,7 @@ export class PreprintProvidersService { getPreprintProvidersAllowingSubmissions(): Observable { return this.jsonApiService - .get< - JsonApiResponse - >(`${this.baseUrl}?filter[allow_submissions]=true`) + .get>(`${this.baseUrl}?filter[allow_submissions]=true`) .pipe( map((response) => { return PreprintProvidersMapper.toPreprintProviderShortInfoFromGetResponse(response.data); @@ -60,12 +57,10 @@ export class PreprintProvidersService { getHighlightedSubjectsByProviderId(providerId: string): Observable { return this.jsonApiService - .get< - JsonApiResponse - >(`${this.baseUrl}${providerId}/subjects/highlighted/?page[size]=20`) + .get(`${this.baseUrl}${providerId}/subjects/highlighted/?page[size]=20`) .pipe( map((response) => { - return PreprintProvidersMapper.fromSubjectsGetResponse(providerId, response.data); + return PreprintProvidersMapper.fromSubjectsGetResponse(response.data); }) ); } diff --git a/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts b/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts index 205e7ee9e..b14ee13cb 100644 --- a/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts +++ b/src/app/features/preprints/store/preprint-providers/preprint-providers.model.ts @@ -1,5 +1,5 @@ -import { PreprintProviderDetails, PreprintProviderShortInfo, Subject } from '@osf/features/preprints/models'; -import { AsyncStateModel } from '@shared/models'; +import { PreprintProviderDetails, PreprintProviderShortInfo } from '@osf/features/preprints/models'; +import { AsyncStateModel, Subject } from '@shared/models'; export interface PreprintProvidersStateModel { preprintProvidersDetails: AsyncStateModel; diff --git a/src/app/shared/models/brand.json-api.model.ts b/src/app/shared/models/brand.json-api.model.ts new file mode 100644 index 000000000..9a59fc814 --- /dev/null +++ b/src/app/shared/models/brand.json-api.model.ts @@ -0,0 +1,12 @@ +export interface BrandGetResponse { + id: string; + type: 'brands'; + attributes: { + name: string; + hero_logo_image: string; + hero_background_image: string; + topnav_logo_image: string; + primary_color: string; + secondary_color: string; + }; +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 5386e39a8..13275a443 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -1,4 +1,5 @@ export * from './addons'; +export * from './brand.json-api.model'; export * from './brand.model'; export * from './charts'; export * from './confirmation-options.model'; diff --git a/src/app/shared/models/subject/subject.model.ts b/src/app/shared/models/subject/subject.model.ts index 83e715e5e..78f50f91a 100644 --- a/src/app/shared/models/subject/subject.model.ts +++ b/src/app/shared/models/subject/subject.model.ts @@ -4,4 +4,5 @@ export interface Subject { children?: Subject[]; parent?: Subject | null; expanded?: boolean; + iri?: string; } diff --git a/src/app/shared/models/subject/subjects-json-api.model.ts b/src/app/shared/models/subject/subjects-json-api.model.ts index 81fd4f296..da42934b6 100644 --- a/src/app/shared/models/subject/subjects-json-api.model.ts +++ b/src/app/shared/models/subject/subjects-json-api.model.ts @@ -10,7 +10,7 @@ export type SubjectDataJsonApi = ApiData< SubjectAttributesJsonApi, SubjectEmbedsJsonApi, SubjectRelationshipsJsonApi, - null + SubjectLinksJsonApi >; interface SubjectAttributesJsonApi { @@ -18,6 +18,10 @@ interface SubjectAttributesJsonApi { taxonomy_name: string; } +interface SubjectLinksJsonApi { + iri: string; +} + interface SubjectRelationshipsJsonApi { parent?: { links: { From cf8cd638c3226d0c80346231574b2705184e4e6e Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 12:21:56 +0300 Subject: [PATCH 06/42] refactor(router): Moved preprint routes to root folder of feature --- src/app/app.routes.ts | 3 +-- src/app/features/preprints/{constants => }/preprints.routes.ts | 0 2 files changed, 1 insertion(+), 2 deletions(-) rename src/app/features/preprints/{constants => }/preprints.routes.ts (100%) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 95e085def..0899c6cf8 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -115,8 +115,7 @@ export const routes: Routes = [ }, { path: 'preprints', - loadChildren: () => - import('./features/preprints/constants/preprints.routes').then((mod) => mod.preprintsRoutes), + loadChildren: () => import('./features/preprints/preprints.routes').then((mod) => mod.preprintsRoutes), }, { path: 'search', diff --git a/src/app/features/preprints/constants/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts similarity index 100% rename from src/app/features/preprints/constants/preprints.routes.ts rename to src/app/features/preprints/preprints.routes.ts From c27d8da783862d3f159e50930390d94450093359 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 13:04:53 +0300 Subject: [PATCH 07/42] refactor(preprints-components): Rename and reorganize component files for consistency --- .../preprints-filter-chips.component.html | 0 .../preprints-filter-chips.component.scss | 0 .../preprints-filter-chips.component.spec.ts | 0 .../preprints-filter-chips.component.ts | 2 +- ...preprints-resources-filters.component.html | 0 ...preprints-resources-filters.component.scss | 0 ...prints-resources-filters.component.spec.ts | 2 +- .../preprints-resources-filters.component.ts | 13 ++++----- .../preprints-resources.component.html | 4 +-- .../preprints-resources.component.scss | 0 .../preprints-resources.component.spec.ts | 0 .../preprints-resources.component.ts | 0 .../preprints-subject-filter.component.html | 0 .../preprints-subject-filter.component.scss | 0 ...preprints-subject-filter.component.spec.ts | 0 .../preprints-subject-filter.component.ts | 0 .../features/preprints/components/index.ts | 28 ++++++++++--------- .../preprint-provider-hero.component.ts | 3 +- .../file-step/file-step.component.html | 0 .../file-step/file-step.component.scss | 0 .../file-step/file-step.component.spec.ts | 0 .../file-step/file-step.component.ts | 0 .../contributors/contributors.component.html | 0 .../contributors/contributors.component.scss | 0 .../contributors.component.spec.ts | 0 .../contributors/contributors.component.ts | 12 ++++---- .../metadata-step.component.html} | 0 .../metadata-step.component.scss} | 0 .../metadata-step.component.spec.ts} | 10 +++---- .../metadata-step/metadata-step.component.ts} | 6 ++-- .../title-and-abstract-step.component.html | 0 .../title-and-abstract-step.component.scss | 0 .../title-and-abstract-step.component.spec.ts | 0 .../title-and-abstract-step.component.ts | 0 .../preprint-provider-discover.component.ts | 5 ++-- .../preprint-provider-overview.component.ts | 9 ++++-- .../submit-preprint-stepper.component.ts | 6 ++-- 37 files changed, 52 insertions(+), 48 deletions(-) rename src/app/features/preprints/components/{ => filters}/preprints-filter-chips/preprints-filter-chips.component.html (100%) rename src/app/features/preprints/components/{ => filters}/preprints-filter-chips/preprints-filter-chips.component.scss (100%) rename src/app/features/preprints/components/{ => filters}/preprints-filter-chips/preprints-filter-chips.component.spec.ts (100%) rename src/app/features/preprints/components/{ => filters}/preprints-filter-chips/preprints-filter-chips.component.ts (97%) rename src/app/features/preprints/components/{ => filters}/preprints-resources-filters/preprints-resources-filters.component.html (100%) rename src/app/features/preprints/components/{ => filters}/preprints-resources-filters/preprints-resources-filters.component.scss (100%) rename src/app/features/preprints/components/{ => filters}/preprints-resources-filters/preprints-resources-filters.component.spec.ts (86%) rename src/app/features/preprints/components/{ => filters}/preprints-resources-filters/preprints-resources-filters.component.ts (78%) rename src/app/features/preprints/components/{ => filters}/preprints-resources/preprints-resources.component.html (96%) rename src/app/features/preprints/components/{ => filters}/preprints-resources/preprints-resources.component.scss (100%) rename src/app/features/preprints/components/{ => filters}/preprints-resources/preprints-resources.component.spec.ts (100%) rename src/app/features/preprints/components/{ => filters}/preprints-resources/preprints-resources.component.ts (100%) rename src/app/features/preprints/components/filters/{preprints-subject => preprints-subject-filter}/preprints-subject-filter.component.html (100%) rename src/app/features/preprints/components/filters/{preprints-subject => preprints-subject-filter}/preprints-subject-filter.component.scss (100%) rename src/app/features/preprints/components/filters/{preprints-subject => preprints-subject-filter}/preprints-subject-filter.component.spec.ts (100%) rename src/app/features/preprints/components/filters/{preprints-subject => preprints-subject-filter}/preprints-subject-filter.component.ts (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/file-step/file-step.component.html (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/file-step/file-step.component.scss (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/file-step/file-step.component.spec.ts (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/file-step/file-step.component.ts (100%) rename src/app/features/preprints/components/{submit-steps/metadata => stepper/metadata-step}/contributors/contributors.component.html (100%) rename src/app/features/preprints/components/{submit-steps/metadata => stepper/metadata-step}/contributors/contributors.component.scss (100%) rename src/app/features/preprints/components/{submit-steps/metadata => stepper/metadata-step}/contributors/contributors.component.spec.ts (100%) rename src/app/features/preprints/components/{submit-steps/metadata => stepper/metadata-step}/contributors/contributors.component.ts (94%) rename src/app/features/preprints/components/{submit-steps/metadata/metadata.component.html => stepper/metadata-step/metadata-step.component.html} (100%) rename src/app/features/preprints/components/{submit-steps/metadata/metadata.component.scss => stepper/metadata-step/metadata-step.component.scss} (100%) rename src/app/features/preprints/components/{submit-steps/metadata/metadata.component.spec.ts => stepper/metadata-step/metadata-step.component.spec.ts} (57%) rename src/app/features/preprints/components/{submit-steps/metadata/metadata.component.ts => stepper/metadata-step/metadata-step.component.ts} (96%) rename src/app/features/preprints/components/{submit-steps => stepper}/title-and-abstract-step/title-and-abstract-step.component.html (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/title-and-abstract-step/title-and-abstract-step.component.scss (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/title-and-abstract-step/title-and-abstract-step.component.spec.ts (100%) rename src/app/features/preprints/components/{submit-steps => stepper}/title-and-abstract-step/title-and-abstract-step.component.ts (100%) diff --git a/src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.html b/src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.html similarity index 100% rename from src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.html rename to src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.html diff --git a/src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.scss b/src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.scss similarity index 100% rename from src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.scss rename to src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.scss diff --git a/src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.spec.ts b/src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.spec.ts similarity index 100% rename from src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.spec.ts rename to src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.spec.ts diff --git a/src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.ts b/src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.ts similarity index 97% rename from src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.ts rename to src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.ts index a0a880991..82a2511eb 100644 --- a/src/app/features/preprints/components/preprints-filter-chips/preprints-filter-chips.component.ts +++ b/src/app/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component.ts @@ -13,7 +13,7 @@ import { SetSubject, } from '@osf/features/preprints/store/preprints-resources-filters'; import { GetAllOptions } from '@osf/features/preprints/store/preprints-resources-filters-options'; -import { FilterType } from '@osf/shared/enums'; +import { FilterType } from '@shared/enums'; @Component({ selector: 'osf-preprints-filter-chips', diff --git a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.html b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.html similarity index 100% rename from src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.html rename to src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.html diff --git a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.scss b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.scss similarity index 100% rename from src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.scss rename to src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.scss diff --git a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.spec.ts b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.spec.ts similarity index 86% rename from src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.spec.ts rename to src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.spec.ts index 1f8c6791e..30480dcd0 100644 --- a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.spec.ts +++ b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { PreprintsResourcesFiltersComponent } from './preprints-resources-filters.component'; +import { PreprintsResourcesFiltersComponent } from '@osf/features/preprints/components'; describe('PreprintsResourcesFiltersComponent', () => { let component: PreprintsResourcesFiltersComponent; diff --git a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.ts b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.ts similarity index 78% rename from src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.ts rename to src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.ts index 8b9f0ebe4..e1052ec1d 100644 --- a/src/app/features/preprints/components/preprints-resources-filters/preprints-resources-filters.component.ts +++ b/src/app/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component.ts @@ -4,15 +4,14 @@ import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'pr import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; -import { - PreprintsCreatorsFilterComponent, - PreprintsDateCreatedFilterComponent, - PreprintsInstitutionFilterComponent, - PreprintsLicenseFilterComponent, - PreprintsSubjectFilterComponent, -} from '@osf/features/preprints/components'; import { PreprintsResourcesFiltersOptionsSelectors } from '@osf/features/preprints/store/preprints-resources-filters-options'; +import { PreprintsCreatorsFilterComponent } from '../preprints-creators-filter/preprints-creators-filter.component'; +import { PreprintsDateCreatedFilterComponent } from '../preprints-date-created-filter/preprints-date-created-filter.component'; +import { PreprintsInstitutionFilterComponent } from '../preprints-institution-filter/preprints-institution-filter.component'; +import { PreprintsLicenseFilterComponent } from '../preprints-license-filter/preprints-license-filter.component'; +import { PreprintsSubjectFilterComponent } from '../preprints-subject-filter/preprints-subject-filter.component'; + @Component({ selector: 'osf-preprints-resources-filters', imports: [ diff --git a/src/app/features/preprints/components/preprints-resources/preprints-resources.component.html b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html similarity index 96% rename from src/app/features/preprints/components/preprints-resources/preprints-resources.component.html rename to src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html index 568041e25..84ab7ee3a 100644 --- a/src/app/features/preprints/components/preprints-resources/preprints-resources.component.html +++ b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html @@ -23,7 +23,7 @@

Sort by:

} @else { @if (isAnyFilterOptions()) { filter bySort by: /> } sort by { - let component: MetadataComponent; - let fixture: ComponentFixture; + let component: MetadataStepComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [MetadataComponent], + imports: [MetadataStepComponent], }).compileComponents(); - fixture = TestBed.createComponent(MetadataComponent); + fixture = TestBed.createComponent(MetadataStepComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts similarity index 96% rename from src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts rename to src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts index c82bb1e9a..ac5a90e6e 100644 --- a/src/app/features/preprints/components/submit-steps/metadata/metadata.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts @@ -45,11 +45,11 @@ import { ContributorsComponent } from './contributors/contributors.component'; LicenseComponent, TagsInputComponent, ], - templateUrl: './metadata.component.html', - styleUrl: './metadata.component.scss', + templateUrl: './metadata-step.component.html', + styleUrl: './metadata-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MetadataComponent implements OnInit { +export class MetadataStepComponent implements OnInit { private actions = createDispatchMap({ createPreprint: CreatePreprint, updatePreprint: UpdatePreprint, diff --git a/src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.html b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html similarity index 100% rename from src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.html rename to src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html diff --git a/src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.scss b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.scss similarity index 100% rename from src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.scss rename to src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.scss diff --git a/src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.spec.ts b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.spec.ts similarity index 100% rename from src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.spec.ts rename to src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.spec.ts diff --git a/src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.ts b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts similarity index 100% rename from src/app/features/preprints/components/submit-steps/title-and-abstract-step/title-and-abstract-step.component.ts rename to src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts diff --git a/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts b/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts index a35e60a4d..c9775a57b 100644 --- a/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts +++ b/src/app/features/preprints/pages/preprint-provider-discover/preprint-provider-discover.component.ts @@ -17,17 +17,16 @@ import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { PreprintsResourcesComponent } from '@osf/features/preprints/components'; -import { PreprintProviderHeroComponent } from '@osf/features/preprints/components/preprint-provider-hero/preprint-provider-hero.component'; +import { PreprintProviderHeroComponent, PreprintsResourcesComponent } from '@osf/features/preprints/components'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { GetResources, + PreprintsDiscoverSelectors, ResetState, SetProviderIri, SetSearchText, SetSortBy, } from '@osf/features/preprints/store/preprints-discover'; -import { PreprintsDiscoverSelectors } from '@osf/features/preprints/store/preprints-discover/preprints-discover.selectors'; import { PreprintsResourcesFiltersSelectors, ResetFiltersState, diff --git a/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts b/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts index 336a96c32..b18263877 100644 --- a/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts +++ b/src/app/features/preprints/pages/preprint-provider-overview/preprint-provider-overview.component.ts @@ -6,9 +6,12 @@ import { ChangeDetectionStrategy, Component, effect, inject, OnDestroy, OnInit } import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; -import { AdvisoryBoardComponent, BrowseBySubjectsComponent } from '@osf/features/preprints/components'; -import { PreprintProviderFooterComponent } from '@osf/features/preprints/components/preprint-provider-footer/preprint-provider-footer.component'; -import { PreprintProviderHeroComponent } from '@osf/features/preprints/components/preprint-provider-hero/preprint-provider-hero.component'; +import { + AdvisoryBoardComponent, + BrowseBySubjectsComponent, + PreprintProviderFooterComponent, + PreprintProviderHeroComponent, +} from '@osf/features/preprints/components'; import { GetHighlightedSubjectsByProviderId, GetPreprintProviderById, diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index 7c8322e52..a9d946efb 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -19,7 +19,7 @@ import { ActivatedRoute } from '@angular/router'; import { FileStepComponent, - MetadataComponent, + MetadataStepComponent, TitleAndAbstractStepComponent, } from '@osf/features/preprints/components'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; @@ -29,13 +29,13 @@ import { ResetStateAndDeletePreprint, SetSelectedPreprintProviderId, } from '@osf/features/preprints/store/submit-preprint'; -import { StepperComponent } from '@shared/components/stepper/stepper.component'; +import { StepperComponent } from '@shared/components'; import { BrandService } from '@shared/services'; import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils'; @Component({ selector: 'osf-submit-preprint-stepper', - imports: [Skeleton, StepperComponent, TitleAndAbstractStepComponent, FileStepComponent, MetadataComponent], + imports: [Skeleton, StepperComponent, TitleAndAbstractStepComponent, FileStepComponent, MetadataStepComponent], templateUrl: './submit-preprint-stepper.component.html', styleUrl: './submit-preprint-stepper.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, From f6bfb320b0cc2ac0c7cffc6bd67f050e97fca0a0 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 13:16:57 +0300 Subject: [PATCH 08/42] fix(preprints-models): Removed unused model --- src/app/features/preprints/models/preprint.models.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index be4f0e67c..4bc8a2be6 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -24,12 +24,3 @@ export interface PreprintFilesLinks { filesLink: string; uploadFileLink: string; } - -export interface SubjectGetResponse { - id: string; - type: 'subjects'; - attributes: { - text: string; - taxonomy_name: string; - }; -} From 189d6cb3e977fb4ae9d5d6e0ab2b230d11b68afa Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 13:35:30 +0300 Subject: [PATCH 09/42] refactor(license): Made license more reusable by removing card wrapper --- .../metadata-step.component.html | 24 ++-- .../components/license/license.component.html | 115 ++++++++---------- .../components/license/license.component.ts | 2 - 3 files changed, 69 insertions(+), 72 deletions(-) diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index 682a46bb2..4ac5f5724 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -5,13 +5,23 @@

Metadata

- + +

{{ 'shared.license.title' | translate }}

+ +

{{ 'shared.license.description' | translate }}

+

+ {{ 'shared.license.helpText' | translate }} + {{ 'common.links.helpGuide' | translate }}. +

+ +
diff --git a/src/app/shared/components/license/license.component.html b/src/app/shared/components/license/license.component.html index da48a4ce0..585f9ca74 100644 --- a/src/app/shared/components/license/license.component.html +++ b/src/app/shared/components/license/license.component.html @@ -1,67 +1,56 @@ - -

{{ 'shared.license.title' | translate }}

- -

{{ 'shared.license.description' | translate }}

-

- {{ 'shared.license.helpText' | translate }} - {{ 'common.links.helpGuide' | translate }}. -

- - - @if (selectedLicense()) { - - @if (selectedLicense()!.requiredFields.length) { -
-
- - -
- +@if (selectedLicense()) { + + @if (selectedLicense()!.requiredFields.length) { + +
+ + - - } +
+ + + } -

- -

+

+ +

- @if (selectedLicense()!.requiredFields.length) { -
- - -
- } + @if (selectedLicense()!.requiredFields.length) { +
+ + +
} -
+} diff --git a/src/app/shared/components/license/license.component.ts b/src/app/shared/components/license/license.component.ts index 27eb3f58f..73ca61aef 100644 --- a/src/app/shared/components/license/license.component.ts +++ b/src/app/shared/components/license/license.component.ts @@ -1,7 +1,6 @@ import { TranslatePipe } from '@ngx-translate/core'; import { Button } from 'primeng/button'; -import { Card } from 'primeng/card'; import { DatePicker } from 'primeng/datepicker'; import { Divider } from 'primeng/divider'; import { Select } from 'primeng/select'; @@ -23,7 +22,6 @@ import { TruncatedTextComponent } from '../truncated-text/truncated-text.compone @Component({ selector: 'osf-license', imports: [ - Card, TranslatePipe, Select, FormsModule, From fe5e33eddef62eb2e41f27dd8323b42be8d76743 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 17:12:43 +0300 Subject: [PATCH 10/42] refactor(metadata-license): Removed redundant wrapper --- .../metadata-step.component.html | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index 4ac5f5724..da7bc2379 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -4,25 +4,23 @@

Metadata

-
- -

{{ 'shared.license.title' | translate }}

+ +

{{ 'shared.license.title' | translate }}

-

{{ 'shared.license.description' | translate }}

-

- {{ 'shared.license.helpText' | translate }} - {{ 'common.links.helpGuide' | translate }}. -

- -
-
+

{{ 'shared.license.description' | translate }}

+

+ {{ 'shared.license.helpText' | translate }} + {{ 'common.links.helpGuide' | translate }}. +

+ +
From 04d15f2cd5af7adb3ebd90de2e60ae480d99ad7b Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 18:23:33 +0300 Subject: [PATCH 11/42] fix(comments): Fixed comments --- .../preprints-resources/preprints-resources.component.html | 4 ++-- .../preprints/models/preprint-provider-json-api.models.ts | 4 ++-- src/app/shared/models/brand.json-api.model.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html index 84ab7ee3a..568041e25 100644 --- a/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html +++ b/src/app/features/preprints/components/filters/preprints-resources/preprints-resources.component.html @@ -23,7 +23,7 @@

Sort by:

} @else { @if (isAnyFilterOptions()) { filter bySort by: /> } sort by Date: Thu, 3 Jul 2025 18:32:07 +0300 Subject: [PATCH 12/42] feat(author-assertions): Partly implemented step with missing available case handling --- .../author-assertions-step.component.html | 224 ++++++++++++++++++ .../author-assertions-step.component.scss | 8 + .../author-assertions-step.component.spec.ts | 22 ++ .../author-assertions-step.component.ts | 218 +++++++++++++++++ .../enums/applicability-status.enum.ts | 5 + src/app/features/preprints/enums/index.ts | 2 + .../preprints/enums/prereg-link-info.enum.ts | 5 + .../preprints/mappers/preprints.mapper.ts | 9 + .../models/preprint-json-api.models.ts | 9 +- .../preprints/models/preprint.models.ts | 10 + .../submit-preprint-stepper.component.html | 3 + .../submit-preprint-stepper.component.ts | 12 +- .../preprints/services/preprints.service.ts | 9 + 13 files changed, 533 insertions(+), 3 deletions(-) create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts create mode 100644 src/app/features/preprints/enums/applicability-status.enum.ts create mode 100644 src/app/features/preprints/enums/prereg-link-info.enum.ts diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html new file mode 100644 index 000000000..690da765a --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html @@ -0,0 +1,224 @@ +

Author Assertions

+ + +
+

Conflict of Interest

+ +

+ The Conflict of Interest (COI) assertion is made on behalf of all the authors listed for this preprint. COIs + include: financial involvement in any entity such as honoraria, grants, speaking fees, employment, consultancies, + stock ownership, expert testimony, and patents or licenses. COIs can also include non-financial interests such as + personal or professional relationships or pre-existing beliefs in the subject matter or materials discussed in + this preprint. +

+ +
+
+ + +
+ +
+ + +
+
+ + + @let coiStatementControl = authorAssertionsForm.controls['coiStatement']; + @if (coiStatementControl.errors?.['required'] && (coiStatementControl.touched || coiStatementControl.dirty)) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } +
+
+ + +
+

Public Data

+ +

+ Data refers to raw and/or processed information (quantitative or qualitative) used for the analyses, case studies, + and/or descriptive interpretation in the preprint. Public data could include data posted to open-access + repositories, public archival library collection, or government archive. For data that is available under limited + circumstances (e.g., after signing a data sharing agreement), choose the ‘No’ option and use the comment box to + explain how others could access the data. +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ + @let hasDataLinks = authorAssertionsForm.value.hasDataLinks!; + @if (hasDataLinks === ApplicabilityStatus.Unavailable || hasDataLinks === ApplicabilityStatus.NotApplicable) { + + @let whyNoDataControl = authorAssertionsForm.controls['whyNoData']; + @if (whyNoDataControl.errors?.['required'] && (whyNoDataControl.touched || whyNoDataControl.dirty)) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } + } @else { +

implement this list of adding links

+ } +
+
+ + +
+

Public Preregistration

+ +

+ A preregistration is a description of the research design and/or analysis plan that is created and registered + before researchers collected data or before they have seen/interacted with preexisting data. The description + should appear in a public registry (e.g., clinicaltrials.gov, OSF, AEA registry). +

+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ @let hasPreregLinks = authorAssertionsForm.value.hasPreregLinks!; + @if (hasPreregLinks === ApplicabilityStatus.Unavailable || hasPreregLinks === ApplicabilityStatus.NotApplicable) { + + @let hasPreregLinksControl = authorAssertionsForm.controls['hasPreregLinks']; + @if (hasPreregLinksControl.errors?.['required'] && (hasPreregLinksControl.touched || hasPreregLinksControl.dirty)) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } + } @else { + + } +
+ +
+ + +
diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss new file mode 100644 index 000000000..18b6612e7 --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss @@ -0,0 +1,8 @@ +@use "assets/styles/mixins" as mix; +@use "assets/styles/variables" as var; + +.card { + @media (max-width: var.$breakpoint-sm) { + --p-card-body-padding: 0.75rem; + } +} diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts new file mode 100644 index 000000000..1d71b74f8 --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AuthorAssertionsStepComponent } from './author-assertions-step.component'; + +describe('AuthorAssertionsComponent', () => { + let component: AuthorAssertionsStepComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AuthorAssertionsStepComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(AuthorAssertionsStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts new file mode 100644 index 000000000..981ffc7fb --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts @@ -0,0 +1,218 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Message } from 'primeng/message'; +import { RadioButton } from 'primeng/radiobutton'; +import { Select } from 'primeng/select'; +import { Textarea } from 'primeng/textarea'; +import { Tooltip } from 'primeng/tooltip'; + +import { NgClass } from '@angular/common'; +import { ChangeDetectionStrategy, Component, effect, HostListener, inject, output } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; + +import { StringOrNull } from '@core/helpers'; +import { formInputLimits } from '@osf/features/preprints/constants'; +import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; +import { SubmitPreprintSelectors, UpdatePreprint } from '@osf/features/preprints/store/submit-preprint'; +import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; +import { ToastService } from '@shared/services'; + +@Component({ + selector: 'osf-author-assertions-step', + imports: [ + Card, + FormsModule, + RadioButton, + ReactiveFormsModule, + Textarea, + Message, + TranslatePipe, + NgClass, + Button, + Tooltip, + Select, + ], + templateUrl: './author-assertions-step.component.html', + styleUrl: './author-assertions-step.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AuthorAssertionsStepComponent { + private toastService = inject(ToastService); + private actions = createDispatchMap({ + updatePreprint: UpdatePreprint, + }); + + readonly ApplicabilityStatus = ApplicabilityStatus; + readonly inputLimits = formInputLimits; + readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + readonly preregLinkOptions = Object.entries(PreregLinkInfo).map(([key, value]) => ({ + label: key, + value, + })); + + createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); + isUpdatingPreprint = select(SubmitPreprintSelectors.isPreprintSubmitting); + + readonly authorAssertionsForm = new FormGroup({ + hasCoi: new FormControl(this.createdPreprint()!.hasCoi, { + nonNullable: true, + validators: [], + }), + coiStatement: new FormControl(this.createdPreprint()!.coiStatement, { + nonNullable: false, + validators: [], + }), + hasDataLinks: new FormControl(this.createdPreprint()!.hasDataLinks, { + nonNullable: true, + validators: [], + }), + whyNoData: new FormControl(this.createdPreprint()!.whyNoData, { + nonNullable: false, + validators: [], + }), + hasPreregLinks: new FormControl(this.createdPreprint()!.hasPreregLinks, { + nonNullable: true, + validators: [], + }), + whyNoPrereg: new FormControl(this.createdPreprint()!.whyNoPrereg, { + nonNullable: false, + validators: [], + }), + preregLinkInfo: new FormControl(this.createdPreprint()!.preregLinkInfo, { + nonNullable: false, + validators: [], + }), + }); + + hasCoiValue = toSignal(this.authorAssertionsForm.controls['hasCoi'].valueChanges, { + initialValue: this.createdPreprint()!.hasCoi, + }); + hasDataLinks = toSignal(this.authorAssertionsForm.controls['hasDataLinks'].valueChanges, { + initialValue: this.createdPreprint()!.hasDataLinks, + }); + hasPreregLinks = toSignal(this.authorAssertionsForm.controls['hasPreregLinks'].valueChanges, { + initialValue: this.createdPreprint()!.hasPreregLinks, + }); + + nextClicked = output(); + + constructor() { + effect(() => { + const hasCoi = this.hasCoiValue(); + const coiStatementControl = this.authorAssertionsForm.controls['coiStatement']; + + if (hasCoi) { + coiStatementControl.setValidators([Validators.required]); + coiStatementControl.enable(); + } else { + coiStatementControl.clearValidators(); + coiStatementControl.setValue(null); + coiStatementControl.disable(); + } + + coiStatementControl.updateValueAndValidity(); + }); + + effect(() => { + const hasDataLinks = this.hasDataLinks(); + const whyNoDataControl = this.authorAssertionsForm.controls['whyNoData']; + + switch (hasDataLinks) { + case ApplicabilityStatus.Unavailable: + whyNoDataControl.setValidators([Validators.required]); + whyNoDataControl.enable(); + break; + case ApplicabilityStatus.NotApplicable: + whyNoDataControl.clearValidators(); + whyNoDataControl.setValue(null); + whyNoDataControl.disable(); + break; + case ApplicabilityStatus.Applicable: + whyNoDataControl.clearValidators(); + whyNoDataControl.setValue(null); + whyNoDataControl.disable(); + break; + } + whyNoDataControl.updateValueAndValidity(); + }); + + effect(() => { + const hasDataLinks = this.hasPreregLinks(); + const whyNoPreregControl = this.authorAssertionsForm.controls['whyNoPrereg']; + const preregLinkInfoControl = this.authorAssertionsForm.controls['preregLinkInfo']; + + switch (hasDataLinks) { + case ApplicabilityStatus.Unavailable: + whyNoPreregControl.setValidators([Validators.required]); + whyNoPreregControl.enable(); + + preregLinkInfoControl.clearValidators(); + preregLinkInfoControl.setValue(null); + break; + case ApplicabilityStatus.NotApplicable: + whyNoPreregControl.clearValidators(); + whyNoPreregControl.setValue(null); + whyNoPreregControl.disable(); + + preregLinkInfoControl.clearValidators(); + preregLinkInfoControl.setValue(null); + break; + case ApplicabilityStatus.Applicable: + whyNoPreregControl.clearValidators(); + whyNoPreregControl.setValue(null); + whyNoPreregControl.disable(); + + preregLinkInfoControl.setValidators([Validators.required]); + break; + } + whyNoPreregControl.updateValueAndValidity(); + preregLinkInfoControl.updateValueAndValidity(); + }); + } + + @HostListener('window:beforeunload', ['$event']) + public onBeforeUnload($event: BeforeUnloadEvent): boolean { + $event.preventDefault(); + return false; + } + + nextButtonClicked() { + const formValue = this.authorAssertionsForm.value; + + const hasCoi = formValue.hasCoi; + const coiStatement = formValue.coiStatement || null; + + const hasDataLinks = formValue.hasDataLinks; + const whyNoData = formValue.whyNoData || null; + const dataLinks: string[] = []; + + const hasPreregLinks = formValue.hasPreregLinks; + const whyNoPrereg = formValue.whyNoPrereg || null; + const preregLinks: string[] = []; + const preregLinkInfo = formValue.preregLinkInfo; + + this.actions + .updatePreprint(this.createdPreprint()!.id, { + hasCoi, + coiStatement, + hasDataLinks, + whyNoData, + dataLinks, + hasPreregLinks, + whyNoPrereg, + preregLinks, + preregLinkInfo, + }) + .subscribe({ + complete: () => { + this.toastService.showSuccess('Preprint saved successfully.'); + this.nextClicked.emit(); + }, + }); + } +} diff --git a/src/app/features/preprints/enums/applicability-status.enum.ts b/src/app/features/preprints/enums/applicability-status.enum.ts new file mode 100644 index 000000000..ec14aaa5a --- /dev/null +++ b/src/app/features/preprints/enums/applicability-status.enum.ts @@ -0,0 +1,5 @@ +export enum ApplicabilityStatus { + Applicable = 'available', + NotApplicable = 'not_applicable', + Unavailable = 'no', +} diff --git a/src/app/features/preprints/enums/index.ts b/src/app/features/preprints/enums/index.ts index 5b8a8b7f9..bf1bf51c4 100644 --- a/src/app/features/preprints/enums/index.ts +++ b/src/app/features/preprints/enums/index.ts @@ -1,2 +1,4 @@ +export { ApplicabilityStatus } from './applicability-status.enum'; export { PreprintFileSource } from './preprint-file-source.enum'; +export { PreregLinkInfo } from './prereg-link-info.enum'; export { SubmitSteps } from './submit-steps.enum'; diff --git a/src/app/features/preprints/enums/prereg-link-info.enum.ts b/src/app/features/preprints/enums/prereg-link-info.enum.ts new file mode 100644 index 000000000..b96928fbf --- /dev/null +++ b/src/app/features/preprints/enums/prereg-link-info.enum.ts @@ -0,0 +1,5 @@ +export enum PreregLinkInfo { + Analysis = 'prereg_analysis', + Designs = 'prereg_designs', + Both = 'prereg_both', +} diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 84d3770af..2661e1388 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -45,6 +45,15 @@ export class PreprintsMapper { copyrightHolders: response.attributes.license_record.copyright_holders.join(','), } : null, + hasCoi: response.attributes.has_coi, + coiStatement: response.attributes.conflict_of_interest_statement, + hasDataLinks: response.attributes.has_data_links, + dataLinks: response.attributes.data_links, + whyNoData: response.attributes.why_no_data, + hasPreregLinks: response.attributes.has_prereg_links, + whyNoPrereg: response.attributes.why_no_prereg, + preregLinks: response.attributes.prereg_links, + preregLinkInfo: response.attributes.prereg_link_info, }; } } diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index f826748ae..27877a0a5 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -1,4 +1,5 @@ import { StringOrNull } from '@core/helpers'; +import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { LicenseRecordJsonApi } from '@shared/models'; export interface PreprintJsonApi { @@ -24,7 +25,13 @@ export interface PreprintJsonApi { is_latest_version: boolean; has_coi: boolean; conflict_of_interest_statement: StringOrNull; - has_data_links: boolean; + has_data_links: ApplicabilityStatus; + data_links: string[]; + why_no_data: StringOrNull; + has_prereg_links: ApplicabilityStatus; + why_no_prereg: StringOrNull; + prereg_links: string[]; + prereg_link_info: PreregLinkInfo | null; } export interface PreprintsRelationshipsJsonApi { diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index 4bc8a2be6..bba170678 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -1,4 +1,5 @@ import { StringOrNull } from '@core/helpers'; +import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { LicenseOptions } from '@shared/models'; export interface Preprint { @@ -18,6 +19,15 @@ export interface Preprint { primaryFileId: StringOrNull; licenseId: StringOrNull; licenseOptions: LicenseOptions | null; + hasCoi: boolean; + coiStatement: StringOrNull; + hasDataLinks: ApplicabilityStatus; + dataLinks: string[]; + whyNoData: StringOrNull; + hasPreregLinks: ApplicabilityStatus; + whyNoPrereg: StringOrNull; + preregLinks: string[]; + preregLinkInfo: PreregLinkInfo | null; } export interface PreprintFilesLinks { diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index da30ac767..0e34f65fd 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -42,6 +42,9 @@

{{ 'Add a ' + preprintProvider()!.preprintWor @case (SubmitStepsEnum.Metadata) { } + @case (SubmitStepsEnum.AuthorAssertions) { + + } @default {

No such step

} diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index a9d946efb..9db2ec3e0 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -22,6 +22,7 @@ import { MetadataStepComponent, TitleAndAbstractStepComponent, } from '@osf/features/preprints/components'; +import { AuthorAssertionsStepComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; import { SubmitSteps } from '@osf/features/preprints/enums'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; @@ -35,7 +36,14 @@ import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils'; @Component({ selector: 'osf-submit-preprint-stepper', - imports: [Skeleton, StepperComponent, TitleAndAbstractStepComponent, FileStepComponent, MetadataStepComponent], + imports: [ + Skeleton, + StepperComponent, + TitleAndAbstractStepComponent, + FileStepComponent, + MetadataStepComponent, + AuthorAssertionsStepComponent, + ], templateUrl: './submit-preprint-stepper.component.html', styleUrl: './submit-preprint-stepper.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -58,7 +66,7 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - currentStep = signal(0); + currentStep = signal(3); isWeb = toSignal(inject(IS_WEB)); constructor() { diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index af95335f2..0668a8060 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -22,6 +22,15 @@ export class PreprintsService { doi: 'doi', customPublicationCitation: 'custom_publication_citation', tags: 'tags', + hasCoi: 'has_coi', + coiStatement: 'conflict_of_interest_statement', + hasDataLinks: 'has_data_links', + dataLinks: 'data_links', + whyNoData: 'why_no_data', + hasPreregLinks: 'has_prereg_links', + preregLinks: 'prereg_links', + whyNoPrereg: 'why_no_prereg', + preregLinkInfo: 'prereg_link_info', }; createPreprint(title: string, abstract: string, providerId: string) { From 14051fe4d3d3530a6349a52c904c7c9c16a72c49 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 19:00:27 +0300 Subject: [PATCH 13/42] refactor(validators): Moved linkValidator from helper function to shared util --- src/app/core/helpers/index.ts | 1 - src/app/core/helpers/link-validator.helper.ts | 16 ---------------- .../developer-app-add-edit-form.component.ts | 7 +++---- .../utils/custom-form-validators.helper.ts | 15 +++++++++++++++ 4 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 src/app/core/helpers/link-validator.helper.ts diff --git a/src/app/core/helpers/index.ts b/src/app/core/helpers/index.ts index 71cd6afca..8a4ee14e8 100644 --- a/src/app/core/helpers/index.ts +++ b/src/app/core/helpers/index.ts @@ -1,4 +1,3 @@ export * from './http.helper'; export * from './i18n.helper'; -export * from './link-validator.helper'; export * from './types.helper'; diff --git a/src/app/core/helpers/link-validator.helper.ts b/src/app/core/helpers/link-validator.helper.ts deleted file mode 100644 index 03052cf5e..000000000 --- a/src/app/core/helpers/link-validator.helper.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; - -export function linkValidator(): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - const value = control.value; - if (!value) { - return null; - } - - const urlPattern = /^(https):\/\/.+/i; - - const isValid = urlPattern.test(value); - - return isValid ? null : { link: true }; - }; -} diff --git a/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts b/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts index 5c21d6e6b..7475ef369 100644 --- a/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts +++ b/src/app/features/settings/developer-apps/components/developer-app-add-edit-form/developer-app-add-edit-form.component.ts @@ -12,8 +12,7 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { Router } from '@angular/router'; -import { linkValidator } from '@osf/core/helpers'; -import { IS_XSMALL } from '@osf/shared/utils'; +import { CustomValidators, IS_XSMALL } from '@osf/shared/utils'; import { DeveloperApp, DeveloperAppCreateUpdate, DeveloperAppForm, DeveloperAppFormFormControls } from '../../models'; import { CreateDeveloperApp, UpdateDeveloperApp } from '../../store'; @@ -41,14 +40,14 @@ export class DeveloperAppAddEditFormComponent implements OnInit { }), [DeveloperAppFormFormControls.ProjectHomePageUrl]: new FormControl('', { nonNullable: true, - validators: [Validators.required, linkValidator()], + validators: [Validators.required, CustomValidators.linkValidator()], }), [DeveloperAppFormFormControls.AppDescription]: new FormControl('', { nonNullable: false, }), [DeveloperAppFormFormControls.AuthorizationCallbackUrl]: new FormControl('', { nonNullable: true, - validators: [Validators.required, linkValidator()], + validators: [Validators.required, CustomValidators.linkValidator()], }), }); diff --git a/src/app/shared/utils/custom-form-validators.helper.ts b/src/app/shared/utils/custom-form-validators.helper.ts index 6e49ab71d..935966456 100644 --- a/src/app/shared/utils/custom-form-validators.helper.ts +++ b/src/app/shared/utils/custom-form-validators.helper.ts @@ -25,4 +25,19 @@ export class CustomValidators { return isValid ? null : { email: { value: control.value } }; }; } + + static linkValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (!value) { + return null; + } + + const urlPattern = /^(https):\/\/.+/i; + + const isValid = urlPattern.test(value); + + return isValid ? null : { link: true }; + }; + } } From ddd89b233a72b76f175440bb9a746483a1015ea5 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 19:32:31 +0300 Subject: [PATCH 14/42] feat(array-input): Implemented reusable component for array input --- .../array-input/array-input.component.html | 17 ++++++++ .../array-input/array-input.component.scss | 0 .../array-input/array-input.component.spec.ts | 22 ++++++++++ .../array-input/array-input.component.ts | 41 +++++++++++++++++++ .../text-input/text-input.component.ts | 4 ++ .../input-validation-messages.const.ts | 1 + src/assets/i18n/en.json | 4 +- 7 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.html create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.scss create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.spec.ts create mode 100644 src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.html new file mode 100644 index 000000000..87227b291 --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.html @@ -0,0 +1,17 @@ +
+
+ @let formArrayControls = formArray().controls; + @for (control of formArrayControls; track $index) { +
+ + @if (formArrayControls.length > 1) { + + } +
+ } +
+ +
+ +
+
diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.scss b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.spec.ts b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.spec.ts new file mode 100644 index 000000000..b29274281 --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ArrayInputComponent } from './array-input.component'; + +describe('ArrayInputComponent', () => { + let component: ArrayInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ArrayInputComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ArrayInputComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts new file mode 100644 index 000000000..39f2fc55d --- /dev/null +++ b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts @@ -0,0 +1,41 @@ +import { Button } from 'primeng/button'; + +import { ChangeDetectionStrategy, Component, input, OnInit } from '@angular/core'; +import { FormArray, FormControl, ReactiveFormsModule, ValidatorFn } from '@angular/forms'; + +import { TextInputComponent } from '@shared/components'; + +@Component({ + selector: 'osf-array-input', + imports: [ReactiveFormsModule, Button, TextInputComponent], + templateUrl: './array-input.component.html', + styleUrl: './array-input.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ArrayInputComponent implements OnInit { + formArray = input.required>(); + inputPlaceholder = input.required(); + validators = input.required(); + + ngOnInit() { + const formArray = this.formArray(); + if (formArray.controls.length === 0) { + this.add(); + } + } + + add() { + this.formArray().push( + new FormControl('', { + nonNullable: true, + validators: this.validators(), + }) + ); + } + + remove(index: number) { + if (this.formArray().length > 1) { + this.formArray().removeAt(index); + } + } +} diff --git a/src/app/shared/components/text-input/text-input.component.ts b/src/app/shared/components/text-input/text-input.component.ts index 3b37fb94d..4a7a027d0 100644 --- a/src/app/shared/components/text-input/text-input.component.ts +++ b/src/app/shared/components/text-input/text-input.component.ts @@ -43,6 +43,10 @@ export class TextInputComponent { return { key: INPUT_VALIDATION_MESSAGES.email }; } + if (errors['link']) { + return { key: INPUT_VALIDATION_MESSAGES.link }; + } + if (errors['maxlength']) return { key: INPUT_VALIDATION_MESSAGES.maxLength, diff --git a/src/app/shared/constants/input-validation-messages.const.ts b/src/app/shared/constants/input-validation-messages.const.ts index ea17320af..8655c9dbc 100644 --- a/src/app/shared/constants/input-validation-messages.const.ts +++ b/src/app/shared/constants/input-validation-messages.const.ts @@ -4,4 +4,5 @@ export const INPUT_VALIDATION_MESSAGES = { maxLength: 'validation.maxLength', minLength: 'validation.minLength', invalidInput: 'validation.invalidInput', + link: 'validation.link', }; diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 0b763e9d1..9dcf7d96b 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -415,7 +415,6 @@ "step2InfoText": "If your project includes components, you can select which components to include or exclude at the end of the registration." }, "selectProject": "Select your project", - "createDraft": "Create draft", "createdSuccessfully": "Draft created successfully" }, @@ -1758,7 +1757,8 @@ "email": "Please enter a valid email address.", "maxLength": "The field must be at most {{length}} characters.", "minLength": "The field must be at least {{length}} characters.", - "invalidInput": "Invalid input." + "invalidInput": "Invalid input.", + "link": "This field must start with http:// or https:// to be a valid url." }, "searchHelpTutorial": { "step1": { From d5477792732e84ba9b375b63590edc20a20707e2 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 3 Jul 2025 19:33:42 +0300 Subject: [PATCH 15/42] feat(array-input): Used array-input for data links and prereg links --- .../author-assertions-step.component.html | 16 +++++++++++++++- .../author-assertions-step.component.ts | 18 ++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html index 690da765a..3bfdf6fac 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html @@ -124,7 +124,13 @@

Public Data

} } @else { -

implement this list of adding links

+
+ +
}

@@ -206,6 +212,14 @@

Public Preregistration

optionValue="value" [showClear]="false" /> + +
+ +
} diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts index 981ffc7fb..06dc74e94 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts @@ -13,14 +13,16 @@ import { Tooltip } from 'primeng/tooltip'; import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, effect, HostListener, inject, output } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { FormArray, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { StringOrNull } from '@core/helpers'; +import { ArrayInputComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component'; import { formInputLimits } from '@osf/features/preprints/constants'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { SubmitPreprintSelectors, UpdatePreprint } from '@osf/features/preprints/store/submit-preprint'; import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; import { ToastService } from '@shared/services'; +import { CustomValidators } from '@shared/utils'; @Component({ selector: 'osf-author-assertions-step', @@ -36,6 +38,7 @@ import { ToastService } from '@shared/services'; Button, Tooltip, Select, + ArrayInputComponent, ], templateUrl: './author-assertions-step.component.html', styleUrl: './author-assertions-step.component.scss', @@ -47,6 +50,7 @@ export class AuthorAssertionsStepComponent { updatePreprint: UpdatePreprint, }); + readonly CustomValidators = CustomValidators; readonly ApplicabilityStatus = ApplicabilityStatus; readonly inputLimits = formInputLimits; readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; @@ -71,6 +75,9 @@ export class AuthorAssertionsStepComponent { nonNullable: true, validators: [], }), + dataLinks: new FormArray( + this.createdPreprint()!.dataLinks?.map((link) => new FormControl(link)) || [] + ), whyNoData: new FormControl(this.createdPreprint()!.whyNoData, { nonNullable: false, validators: [], @@ -79,6 +86,9 @@ export class AuthorAssertionsStepComponent { nonNullable: true, validators: [], }), + preregLinks: new FormArray( + this.createdPreprint()!.preregLinks?.map((link) => new FormControl(link)) || [] + ), whyNoPrereg: new FormControl(this.createdPreprint()!.whyNoPrereg, { nonNullable: false, validators: [], @@ -189,12 +199,12 @@ export class AuthorAssertionsStepComponent { const hasDataLinks = formValue.hasDataLinks; const whyNoData = formValue.whyNoData || null; - const dataLinks: string[] = []; + const dataLinks: string[] = formValue.dataLinks || []; const hasPreregLinks = formValue.hasPreregLinks; const whyNoPrereg = formValue.whyNoPrereg || null; - const preregLinks: string[] = []; - const preregLinkInfo = formValue.preregLinkInfo; + const preregLinks: string[] = formValue.preregLinks || []; + const preregLinkInfo = formValue.preregLinkInfo || undefined; this.actions .updatePreprint(this.createdPreprint()!.id, { From 83a6b8ed58c63d6a0b3ad89f753c3163d6f20850 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 12:20:31 +0300 Subject: [PATCH 16/42] refactor(author-assertions): Refactored step. Fixed minor issues --- .../array-input/array-input.component.ts | 11 +- .../author-assertions-step.component.html | 10 +- .../author-assertions-step.component.ts | 125 ++++++++++++------ .../models/preprint-json-api.models.ts | 8 +- .../preprints/models/preprint.models.ts | 8 +- 5 files changed, 96 insertions(+), 66 deletions(-) diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts index 39f2fc55d..a65ad89f2 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component.ts @@ -1,6 +1,6 @@ import { Button } from 'primeng/button'; -import { ChangeDetectionStrategy, Component, input, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { FormArray, FormControl, ReactiveFormsModule, ValidatorFn } from '@angular/forms'; import { TextInputComponent } from '@shared/components'; @@ -12,18 +12,11 @@ import { TextInputComponent } from '@shared/components'; styleUrl: './array-input.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ArrayInputComponent implements OnInit { +export class ArrayInputComponent { formArray = input.required>(); inputPlaceholder = input.required(); validators = input.required(); - ngOnInit() { - const formArray = this.formArray(); - if (formArray.controls.length === 0) { - this.add(); - } - } - add() { this.formArray().push( new FormControl('', { diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html index 3bfdf6fac..dd7e22bb7 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html @@ -128,7 +128,7 @@

Public Data

} @@ -154,7 +154,7 @@

Public Preregistration

[value]="ApplicabilityStatus.Applicable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@@ -164,7 +164,7 @@

Public Preregistration

[value]="ApplicabilityStatus.Unavailable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@@ -174,7 +174,7 @@

Public Preregistration

[value]="ApplicabilityStatus.NotApplicable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@let hasPreregLinks = authorAssertionsForm.value.hasPreregLinks!; @@ -217,7 +217,7 @@

Public Preregistration

} diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts index 06dc74e94..9af511445 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts @@ -13,7 +13,16 @@ import { Tooltip } from 'primeng/tooltip'; import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, effect, HostListener, inject, output } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; -import { FormArray, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { + AbstractControl, + FormArray, + FormControl, + FormGroup, + FormsModule, + ReactiveFormsModule, + ValidatorFn, + Validators, +} from '@angular/forms'; import { StringOrNull } from '@core/helpers'; import { ArrayInputComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component'; @@ -58,12 +67,13 @@ export class AuthorAssertionsStepComponent { label: key, value, })); + readonly linkValidators = [CustomValidators.linkValidator(), CustomValidators.requiredTrimmed()]; createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); isUpdatingPreprint = select(SubmitPreprintSelectors.isPreprintSubmitting); readonly authorAssertionsForm = new FormGroup({ - hasCoi: new FormControl(this.createdPreprint()!.hasCoi, { + hasCoi: new FormControl(this.createdPreprint()!.hasCoi || false, { nonNullable: true, validators: [], }), @@ -71,10 +81,13 @@ export class AuthorAssertionsStepComponent { nonNullable: false, validators: [], }), - hasDataLinks: new FormControl(this.createdPreprint()!.hasDataLinks, { - nonNullable: true, - validators: [], - }), + hasDataLinks: new FormControl( + this.createdPreprint()!.hasDataLinks || ApplicabilityStatus.NotApplicable, + { + nonNullable: true, + validators: [], + } + ), dataLinks: new FormArray( this.createdPreprint()!.dataLinks?.map((link) => new FormControl(link)) || [] ), @@ -82,10 +95,13 @@ export class AuthorAssertionsStepComponent { nonNullable: false, validators: [], }), - hasPreregLinks: new FormControl(this.createdPreprint()!.hasPreregLinks, { - nonNullable: true, - validators: [], - }), + hasPreregLinks: new FormControl( + this.createdPreprint()!.hasPreregLinks || ApplicabilityStatus.NotApplicable, + { + nonNullable: true, + validators: [], + } + ), preregLinks: new FormArray( this.createdPreprint()!.preregLinks?.map((link) => new FormControl(link)) || [] ), @@ -100,13 +116,13 @@ export class AuthorAssertionsStepComponent { }); hasCoiValue = toSignal(this.authorAssertionsForm.controls['hasCoi'].valueChanges, { - initialValue: this.createdPreprint()!.hasCoi, + initialValue: this.createdPreprint()!.hasCoi || false, }); hasDataLinks = toSignal(this.authorAssertionsForm.controls['hasDataLinks'].valueChanges, { - initialValue: this.createdPreprint()!.hasDataLinks, + initialValue: this.createdPreprint()!.hasDataLinks || ApplicabilityStatus.NotApplicable, }); hasPreregLinks = toSignal(this.authorAssertionsForm.controls['hasPreregLinks'].valueChanges, { - initialValue: this.createdPreprint()!.hasPreregLinks, + initialValue: this.createdPreprint()!.hasPreregLinks || ApplicabilityStatus.NotApplicable, }); nextClicked = output(); @@ -117,12 +133,9 @@ export class AuthorAssertionsStepComponent { const coiStatementControl = this.authorAssertionsForm.controls['coiStatement']; if (hasCoi) { - coiStatementControl.setValidators([Validators.required]); - coiStatementControl.enable(); + this.enableAndSetValidators(coiStatementControl, [Validators.required]); } else { - coiStatementControl.clearValidators(); - coiStatementControl.setValue(null); - coiStatementControl.disable(); + this.disableAndClearValidators(coiStatementControl); } coiStatementControl.updateValueAndValidity(); @@ -131,57 +144,52 @@ export class AuthorAssertionsStepComponent { effect(() => { const hasDataLinks = this.hasDataLinks(); const whyNoDataControl = this.authorAssertionsForm.controls['whyNoData']; + const dataLinksControl = this.authorAssertionsForm.controls['dataLinks']; switch (hasDataLinks) { case ApplicabilityStatus.Unavailable: - whyNoDataControl.setValidators([Validators.required]); - whyNoDataControl.enable(); + this.enableAndSetValidators(whyNoDataControl, [Validators.required]); + this.disableAndClearValidators(dataLinksControl); break; case ApplicabilityStatus.NotApplicable: - whyNoDataControl.clearValidators(); - whyNoDataControl.setValue(null); - whyNoDataControl.disable(); + this.disableAndClearValidators(whyNoDataControl); + this.disableAndClearValidators(dataLinksControl); break; case ApplicabilityStatus.Applicable: - whyNoDataControl.clearValidators(); - whyNoDataControl.setValue(null); - whyNoDataControl.disable(); + this.disableAndClearValidators(whyNoDataControl); + this.addAtLeastOneControl(dataLinksControl); break; } whyNoDataControl.updateValueAndValidity(); + dataLinksControl.updateValueAndValidity(); }); effect(() => { - const hasDataLinks = this.hasPreregLinks(); + const hasPreregLinks = this.hasPreregLinks(); const whyNoPreregControl = this.authorAssertionsForm.controls['whyNoPrereg']; const preregLinkInfoControl = this.authorAssertionsForm.controls['preregLinkInfo']; + const preregLinksControl = this.authorAssertionsForm.controls['preregLinks']; - switch (hasDataLinks) { + switch (hasPreregLinks) { case ApplicabilityStatus.Unavailable: - whyNoPreregControl.setValidators([Validators.required]); - whyNoPreregControl.enable(); - - preregLinkInfoControl.clearValidators(); - preregLinkInfoControl.setValue(null); + this.enableAndSetValidators(whyNoPreregControl, [Validators.required]); + this.disableAndClearValidators(preregLinkInfoControl); + this.disableAndClearValidators(preregLinksControl); break; case ApplicabilityStatus.NotApplicable: - whyNoPreregControl.clearValidators(); - whyNoPreregControl.setValue(null); - whyNoPreregControl.disable(); - - preregLinkInfoControl.clearValidators(); - preregLinkInfoControl.setValue(null); + this.disableAndClearValidators(whyNoPreregControl); + this.disableAndClearValidators(preregLinkInfoControl); + this.disableAndClearValidators(preregLinksControl); break; case ApplicabilityStatus.Applicable: - whyNoPreregControl.clearValidators(); - whyNoPreregControl.setValue(null); - whyNoPreregControl.disable(); - - preregLinkInfoControl.setValidators([Validators.required]); + this.disableAndClearValidators(whyNoPreregControl); + this.enableAndSetValidators(preregLinkInfoControl, [Validators.required]); + this.addAtLeastOneControl(preregLinksControl); break; } whyNoPreregControl.updateValueAndValidity(); preregLinkInfoControl.updateValueAndValidity(); + preregLinksControl.updateValueAndValidity(); }); } @@ -225,4 +233,33 @@ export class AuthorAssertionsStepComponent { }, }); } + + private disableAndClearValidators(control: AbstractControl) { + if (control instanceof FormArray) { + while (control.length !== 0) { + control.removeAt(0); + } + return; + } + + control.clearValidators(); + control.setValue(null); + control.disable(); + } + + private enableAndSetValidators(control: AbstractControl, validators: ValidatorFn[]) { + control.setValidators(validators); + control.enable(); + } + + private addAtLeastOneControl(formArray: FormArray) { + if (formArray.controls.length > 0) return; + + formArray.push( + new FormControl('', { + nonNullable: true, + validators: this.linkValidators, + }) + ); + } } diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index 27877a0a5..44ae1cd64 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -1,4 +1,4 @@ -import { StringOrNull } from '@core/helpers'; +import { BooleanOrNull, StringOrNull } from '@core/helpers'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { LicenseRecordJsonApi } from '@shared/models'; @@ -23,12 +23,12 @@ export interface PreprintJsonApi { date_last_transitioned: Date | null; version: number; is_latest_version: boolean; - has_coi: boolean; + has_coi: BooleanOrNull; conflict_of_interest_statement: StringOrNull; - has_data_links: ApplicabilityStatus; + has_data_links: ApplicabilityStatus | null; data_links: string[]; why_no_data: StringOrNull; - has_prereg_links: ApplicabilityStatus; + has_prereg_links: ApplicabilityStatus | null; why_no_prereg: StringOrNull; prereg_links: string[]; prereg_link_info: PreregLinkInfo | null; diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index bba170678..7f797cd77 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -1,4 +1,4 @@ -import { StringOrNull } from '@core/helpers'; +import { BooleanOrNull, StringOrNull } from '@core/helpers'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { LicenseOptions } from '@shared/models'; @@ -19,12 +19,12 @@ export interface Preprint { primaryFileId: StringOrNull; licenseId: StringOrNull; licenseOptions: LicenseOptions | null; - hasCoi: boolean; + hasCoi: BooleanOrNull; coiStatement: StringOrNull; - hasDataLinks: ApplicabilityStatus; + hasDataLinks: ApplicabilityStatus | null; dataLinks: string[]; whyNoData: StringOrNull; - hasPreregLinks: ApplicabilityStatus; + hasPreregLinks: ApplicabilityStatus | null; whyNoPrereg: StringOrNull; preregLinks: string[]; preregLinkInfo: PreregLinkInfo | null; From 26e4f44807db3cb82267bb3b837b2490a55ea589 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 13:04:39 +0300 Subject: [PATCH 17/42] fix(translations): Update URL validation message for clarity --- src/assets/i18n/en.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 02b867e76..ad612fb3b 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -1610,7 +1610,6 @@ "step2InfoText": "If your project includes components, you can select which components to include or exclude at the end of the registration." }, "selectProject": "Select your project", - "createDraft": "Create draft", "createdSuccessfully": "Draft created successfully" }, @@ -1746,7 +1745,7 @@ "maxLength": "The field must be at most {{length}} characters.", "minLength": "The field must be at least {{length}} characters.", "invalidInput": "Invalid input.", - "link": "This field must https:// to be a valid url." + "link": "This field must start with https:// to be a valid url." }, "searchHelpTutorial": { "step1": { From 395976404245048a0a550d41f4d8aec2fdd3c415 Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 13:42:05 +0300 Subject: [PATCH 18/42] fix(comments): Fixed comments --- .../author-assertion-step/author-assertions-step.component.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss index 18b6612e7..243cc50eb 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.scss @@ -1,4 +1,3 @@ -@use "assets/styles/mixins" as mix; @use "assets/styles/variables" as var; .card { From 011a6085b5ee1035bd4ce9a6d2a208c7ab21678d Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 15:44:27 +0300 Subject: [PATCH 19/42] refactor(subjects): Refactored shared subjects component and service --- .../registries-subjects.component.html | 5 +---- .../registries-subjects.component.ts | 7 +------ .../services/registration-subjects.service.ts | 11 ++++++++--- .../components/subjects/subjects.component.html | 2 +- .../components/subjects/subjects.component.ts | 14 +++++++++----- .../shared/models/subject/subject-service.model.ts | 3 ++- src/app/shared/stores/subjects/subjects.actions.ts | 8 +++++++- src/app/shared/stores/subjects/subjects.state.ts | 4 ++-- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html index 1e83c52cd..028448206 100644 --- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html +++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.html @@ -1,9 +1,6 @@ { + getSubjects(providerId: string, search?: string): Observable { const params: Record = { 'page[size]': '100', sort: 'text', @@ -28,7 +28,7 @@ export class RegistrationSubjectsService implements ISubjectsService { params['embed'] = 'parent'; } return this.jsonApiService - .get(`${this.apiUrl}/providers/registrations/osf/subjects/`, params) + .get(`${this.apiUrl}/providers/registrations/${providerId}/subjects/`, params) .pipe( map((response) => { return SubjectMapper.fromSubjectsResponseJsonApi(response); @@ -54,8 +54,13 @@ export class RegistrationSubjectsService implements ISubjectsService { } getRegistrationSubjects(draftId: string): Observable { + const params: Record = { + 'page[size]': '100', + page: '1', + }; + return this.jsonApiService - .get(`${this.apiUrl}/draft_registrations/${draftId}/subjects/`) + .get(`${this.apiUrl}/draft_registrations/${draftId}/subjects/`, params) .pipe( map((response) => { return SubjectMapper.fromSubjectsResponseJsonApi(response); diff --git a/src/app/shared/components/subjects/subjects.component.html b/src/app/shared/components/subjects/subjects.component.html index 2ab6c967b..6fa8063ea 100644 --- a/src/app/shared/components/subjects/subjects.component.html +++ b/src/app/shared/components/subjects/subjects.component.html @@ -50,7 +50,7 @@

{{ 'shared.subjects.title' | translate }}

selectionMode="checkbox" [selection]="selectedTree()" [lazy]="true" - [loading]="loading()" + [loading]="areSubjectsUpdating() || subjectsLoading()" (onNodeExpand)="loadNode($event.node)" (onNodeCollapse)="collapseNode($event.node)" (onNodeSelect)="selectSubject($event.node.data)" diff --git a/src/app/shared/components/subjects/subjects.component.ts b/src/app/shared/components/subjects/subjects.component.ts index d6fc72244..5f76fe0e6 100644 --- a/src/app/shared/components/subjects/subjects.component.ts +++ b/src/app/shared/components/subjects/subjects.component.ts @@ -1,3 +1,5 @@ +import { select } from '@ngxs/store'; + import { TranslatePipe } from '@ngx-translate/core'; import { TreeNode } from 'primeng/api'; @@ -13,6 +15,7 @@ import { ChangeDetectionStrategy, Component, computed, input, output } from '@an import { FormControl } from '@angular/forms'; import { Subject } from '@osf/shared/models'; +import { SubjectsSelectors } from '@shared/stores'; import { SearchInputComponent } from '../search-input/search-input.component'; @@ -24,16 +27,17 @@ import { SearchInputComponent } from '../search-input/search-input.component'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class SubjectsComponent { - list = input([]); - searchedSubjects = input([]); - loading = input(false); - isSearching = input(false); + subjects = select(SubjectsSelectors.getSubjects); + subjectsLoading = select(SubjectsSelectors.getSubjectsLoading); + searchedSubjects = select(SubjectsSelectors.getSearchedSubjects); + areSubjectsUpdating = input(false); + isSearching = select(SubjectsSelectors.getSearchedSubjectsLoading); selected = input([]); searchChanged = output(); loadChildren = output(); updateSelection = output(); - subjectsTree = computed(() => this.list().map((subject: Subject) => this.mapSubjectToTreeNode(subject))); + subjectsTree = computed(() => this.subjects().map((subject: Subject) => this.mapSubjectToTreeNode(subject))); selectedTree = computed(() => this.selected().map((subject: Subject) => this.mapSubjectToTreeNode(subject))); searchedList = computed(() => this.searchedSubjects().map((subject: Subject) => this.mapParentsSubject(subject))); expanded: Record = {}; diff --git a/src/app/shared/models/subject/subject-service.model.ts b/src/app/shared/models/subject/subject-service.model.ts index b9b5ae941..56d8abcb8 100644 --- a/src/app/shared/models/subject/subject-service.model.ts +++ b/src/app/shared/models/subject/subject-service.model.ts @@ -3,6 +3,7 @@ import { Observable } from 'rxjs'; import { Subject } from './subject.model'; export interface ISubjectsService { - getSubjects(search?: string): Observable; + getSubjects(providerId: string, search?: string): Observable; + getChildrenSubjects(parentId: string): Observable; } diff --git a/src/app/shared/stores/subjects/subjects.actions.ts b/src/app/shared/stores/subjects/subjects.actions.ts index d2a92eda6..6be7d53ca 100644 --- a/src/app/shared/stores/subjects/subjects.actions.ts +++ b/src/app/shared/stores/subjects/subjects.actions.ts @@ -4,6 +4,7 @@ export class GetSubjects { export class UpdateProjectSubjects { static readonly type = '[Subjects] Update Project'; + constructor( public projectId: string, public subjectIds: string[] @@ -12,10 +13,15 @@ export class UpdateProjectSubjects { export class FetchSubjects { static readonly type = '[Subjects] Fetch Subjects'; - constructor(public search?: string) {} + + constructor( + public providerId: string, + public search?: string + ) {} } export class FetchChildrenSubjects { static readonly type = '[Subjects] Fetch Children Subjects'; + constructor(public parentId: string) {} } diff --git a/src/app/shared/stores/subjects/subjects.state.ts b/src/app/shared/stores/subjects/subjects.state.ts index c1cbfadf6..7b30563ef 100644 --- a/src/app/shared/stores/subjects/subjects.state.ts +++ b/src/app/shared/stores/subjects/subjects.state.ts @@ -39,7 +39,7 @@ export class SubjectsState { private readonly subjectsService = inject(SUBJECTS_SERVICE); @Action(FetchSubjects) - fetchSubjects(ctx: StateContext, { search }: FetchSubjects) { + fetchSubjects(ctx: StateContext, { providerId, search }: FetchSubjects) { ctx.patchState({ subjects: { ...ctx.getState().subjects, @@ -53,7 +53,7 @@ export class SubjectsState { }, }); - return this.subjectsService.getSubjects(search).pipe( + return this.subjectsService.getSubjects(providerId, search).pipe( tap((subjects) => { if (search) { ctx.patchState({ From 4732431b869abe7c5e5c60cea4d8bcdb95cdaa3d Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 15:50:06 +0300 Subject: [PATCH 20/42] feat(metadata-subjects): Implemented Subjects section on metadata step --- .../metadata-step.component.html | 4 + .../metadata-step/metadata-step.component.ts | 2 + .../preprints-subjects.component.html | 7 + .../preprints-subjects.component.scss | 0 .../preprints-subjects.component.spec.ts | 22 +++ .../preprints-subjects.component.ts | 49 +++++ .../features/preprints/preprints.routes.ts | 8 + src/app/features/preprints/services/index.ts | 1 + .../services/preprint-subjects.service.ts | 78 ++++++++ .../submit-preprint.actions.ts | 12 +- .../submit-preprint/submit-preprint.model.ts | 3 +- .../submit-preprint.selectors.ts | 10 + .../submit-preprint/submit-preprint.state.ts | 175 ++++++++++-------- 13 files changed, 289 insertions(+), 82 deletions(-) create mode 100644 src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html create mode 100644 src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.scss create mode 100644 src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts create mode 100644 src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts create mode 100644 src/app/features/preprints/services/preprint-subjects.service.ts diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index da7bc2379..19e9a2867 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -41,6 +41,10 @@

Publication DOI

+
+ +
+

Tags (optional)

diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts index ac5a90e6e..31440d136 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts @@ -12,6 +12,7 @@ import { Tooltip } from 'primeng/tooltip'; import { ChangeDetectionStrategy, Component, HostListener, OnInit, output } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { PreprintsSubjectsComponent } from '@osf/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component'; import { formInputLimits } from '@osf/features/preprints/constants'; import { MetadataForm, Preprint } from '@osf/features/preprints/models'; import { @@ -44,6 +45,7 @@ import { ContributorsComponent } from './contributors/contributors.component'; Tooltip, LicenseComponent, TagsInputComponent, + PreprintsSubjectsComponent, ], templateUrl: './metadata-step.component.html', styleUrl: './metadata-step.component.scss', diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html new file mode 100644 index 000000000..4b926a1c0 --- /dev/null +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html @@ -0,0 +1,7 @@ + diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.scss b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts new file mode 100644 index 000000000..dfc8c1641 --- /dev/null +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PreprintsSubjectsComponent } from './preprints-subjects.component'; + +describe('RegistriesSubjectsComponent', () => { + let component: PreprintsSubjectsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PreprintsSubjectsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(PreprintsSubjectsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts new file mode 100644 index 000000000..23a7556d2 --- /dev/null +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts @@ -0,0 +1,49 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; + +import { + FetchPreprintsSubjects, + SubmitPreprintSelectors, + UpdatePreprintsSubjects, +} from '@osf/features/preprints/store/submit-preprint'; +import { SubjectsComponent } from '@osf/shared/components'; +import { Subject } from '@osf/shared/models'; +import { FetchChildrenSubjects, FetchSubjects } from '@osf/shared/stores'; + +@Component({ + selector: 'osf-preprints-subjects', + imports: [SubjectsComponent], + templateUrl: './preprints-subjects.component.html', + styleUrl: './preprints-subjects.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PreprintsSubjectsComponent implements OnInit { + private readonly selectedProviderId = select(SubmitPreprintSelectors.getSelectedProviderId); + protected selectedSubjects = select(SubmitPreprintSelectors.getSelectedSubjects); + protected isSubjectsUpdating = select(SubmitPreprintSelectors.isSubjectsUpdating); + + protected actions = createDispatchMap({ + fetchSubjects: FetchSubjects, + fetchPreprintsSubjects: FetchPreprintsSubjects, + fetchChildrenSubjects: FetchChildrenSubjects, + updatePreprintsSubjects: UpdatePreprintsSubjects, + }); + + ngOnInit(): void { + this.actions.fetchSubjects(this.selectedProviderId()!); + this.actions.fetchPreprintsSubjects(); + } + + getSubjectChildren(parentId: string) { + this.actions.fetchChildrenSubjects(parentId); + } + + searchSubjects(search: string) { + this.actions.fetchSubjects(search); + } + + updateSelectedSubjects(subjects: Subject[]) { + this.actions.updatePreprintsSubjects(subjects); + } +} diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index f1ac2a629..49529d677 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -3,12 +3,15 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { PreprintsComponent } from '@osf/features/preprints/preprints.component'; +import { PreprintSubjectsService } from '@osf/features/preprints/services'; import { PreprintProvidersState } from '@osf/features/preprints/store/preprint-providers'; import { PreprintsDiscoverState } from '@osf/features/preprints/store/preprints-discover'; import { PreprintsResourcesFiltersState } from '@osf/features/preprints/store/preprints-resources-filters'; import { PreprintsResourcesFiltersOptionsState } from '@osf/features/preprints/store/preprints-resources-filters-options'; import { SubmitPreprintState } from '@osf/features/preprints/store/submit-preprint'; import { ContributorsState } from '@shared/components/contributors/store'; +import { SubjectsState } from '@shared/stores'; +import { SUBJECTS_SERVICE } from '@shared/tokens/subjects.token'; export const preprintsRoutes: Routes = [ { @@ -22,7 +25,12 @@ export const preprintsRoutes: Routes = [ PreprintsResourcesFiltersOptionsState, SubmitPreprintState, ContributorsState, + SubjectsState, ]), + { + provide: SUBJECTS_SERVICE, + useClass: PreprintSubjectsService, + }, ], children: [ { diff --git a/src/app/features/preprints/services/index.ts b/src/app/features/preprints/services/index.ts index 339550d00..da9019e74 100644 --- a/src/app/features/preprints/services/index.ts +++ b/src/app/features/preprints/services/index.ts @@ -2,5 +2,6 @@ export { PreprintContributorsService } from './preprint-contributors.service'; export { PreprintFilesService } from './preprint-files.service'; export { PreprintLicensesService } from './preprint-licenses.service'; export { PreprintProvidersService } from './preprint-providers.service'; +export { PreprintSubjectsService } from './preprint-subjects.service'; export { PreprintsService } from './preprints.service'; export { PreprintsFiltersOptionsService } from './preprints-resource-filters.service'; diff --git a/src/app/features/preprints/services/preprint-subjects.service.ts b/src/app/features/preprints/services/preprint-subjects.service.ts new file mode 100644 index 000000000..d41c3421a --- /dev/null +++ b/src/app/features/preprints/services/preprint-subjects.service.ts @@ -0,0 +1,78 @@ +import { map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { JsonApiService } from '@osf/core/services'; +import { SubjectMapper } from '@osf/shared/mappers'; +import { ISubjectsService, Subject, SubjectsResponseJsonApi } from '@osf/shared/models'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class PreprintSubjectsService implements ISubjectsService { + private apiUrl = environment.apiUrl; + private readonly jsonApiService = inject(JsonApiService); + + getSubjects(providerId: string, search?: string): Observable { + const params: Record = { + 'page[size]': '100', + sort: 'text', + related_counts: 'children', + 'filter[parent]': 'null', + }; + if (search) { + delete params['filter[parent]']; + params['filter[text]'] = search; + params['embed'] = 'parent'; + } + return this.jsonApiService + .get(`${this.apiUrl}/providers/preprints/${providerId}/subjects/`, params) + .pipe( + map((response) => { + return SubjectMapper.fromSubjectsResponseJsonApi(response); + }) + ); + } + + getChildrenSubjects(parentId: string): Observable { + const params: Record = { + 'page[size]': '100', + page: '1', + sort: 'text', + related_counts: 'children', + }; + + return this.jsonApiService + .get(`${this.apiUrl}/subjects/${parentId}/children/`, params) + .pipe( + map((response) => { + return SubjectMapper.fromSubjectsResponseJsonApi(response); + }) + ); + } + + getPreprintSubjects(preprintId: string): Observable { + const params: Record = { + 'page[size]': '100', + page: '1', + }; + + return this.jsonApiService + .get(`${this.apiUrl}/preprints/${preprintId}/subjects/`, params) + .pipe( + map((response) => { + return SubjectMapper.fromSubjectsResponseJsonApi(response); + }) + ); + } + + updatePreprintSubjects(preprintId: string, subjects: Subject[]): Observable { + const payload = { + data: subjects.map((item) => ({ id: item.id, type: 'subjects' })), + }; + + return this.jsonApiService.put(`${this.apiUrl}/preprints/${preprintId}/relationships/subjects/`, payload); + } +} diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts index 9c3b26913..6b2b90729 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts @@ -2,7 +2,7 @@ import { StringOrNull } from '@core/helpers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint } from '@osf/features/preprints/models'; import { ContributorAddModel, ContributorModel } from '@shared/components/contributors/models'; -import { LicenseOptions, OsfFile } from '@shared/models'; +import { LicenseOptions, OsfFile, Subject } from '@shared/models'; export class SetSelectedPreprintProviderId { static readonly type = '[Submit Preprint] Set Selected Preprint Provider Id'; @@ -114,6 +114,16 @@ export class SaveLicense { ) {} } +export class FetchPreprintsSubjects { + static readonly type = '[Submit Preprint] Fetch Registration Subjects'; +} + +export class UpdatePreprintsSubjects { + static readonly type = '[Submit Preprint] Update Registration Subject'; + + constructor(public subjects: Subject[]) {} +} + export class ResetStateAndDeletePreprint { static readonly type = '[Submit Preprint] Reset State And Delete Preprint'; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts index 8a3ea7575..85c7f0676 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts @@ -2,7 +2,7 @@ import { StringOrNull } from '@core/helpers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint, PreprintFilesLinks } from '@osf/features/preprints/models'; import { ContributorModel } from '@shared/components/contributors/models'; -import { AsyncStateModel, IdName, OsfFile } from '@shared/models'; +import { AsyncStateModel, IdName, OsfFile, Subject } from '@shared/models'; import { License } from '@shared/models/license.model'; export interface SubmitPreprintStateModel { @@ -15,4 +15,5 @@ export interface SubmitPreprintStateModel { projectFiles: AsyncStateModel; contributors: AsyncStateModel; licenses: AsyncStateModel; + subjects: AsyncStateModel; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts index 5eb2a2d39..565ede534 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts @@ -72,4 +72,14 @@ export class SubmitPreprintSelectors { static getLicenses(state: SubmitPreprintStateModel) { return state.licenses.data; } + + @Selector([SubmitPreprintState]) + static getSelectedSubjects(state: SubmitPreprintStateModel) { + return state.subjects.data; + } + + @Selector([SubmitPreprintState]) + static isSubjectsUpdating(state: SubmitPreprintStateModel) { + return state.subjects.isLoading; + } } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 4b34b0d07..bc1b00dac 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -7,6 +7,7 @@ import { catchError } from 'rxjs/operators'; import { HttpEventType } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; +import { handleSectionError } from '@core/handlers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint } from '@osf/features/preprints/models'; import { @@ -14,6 +15,7 @@ import { PreprintFilesService, PreprintLicensesService, PreprintsService, + PreprintSubjectsService, } from '@osf/features/preprints/services'; import { OsfFile } from '@shared/models'; import { FilesService } from '@shared/services'; @@ -25,6 +27,7 @@ import { DeleteContributor, FetchContributors, FetchLicenses, + FetchPreprintsSubjects, GetAvailableProjects, GetPreprintFiles, GetPreprintFilesLinks, @@ -38,51 +41,59 @@ import { SubmitPreprintStateModel, UpdateContributor, UpdatePreprint, + UpdatePreprintsSubjects, UploadFile, } from './'; +const DefaultState: SubmitPreprintStateModel = { + selectedProviderId: null, + createdPreprint: { + data: null, + isLoading: false, + error: null, + isSubmitting: false, + }, + fileSource: PreprintFileSource.None, + preprintFilesLinks: { + data: null, + isLoading: false, + error: null, + }, + preprintFiles: { + data: [], + isLoading: false, + error: null, + }, + availableProjects: { + data: [], + isLoading: false, + error: null, + }, + projectFiles: { + data: [], + isLoading: false, + error: null, + }, + contributors: { + data: [], + isLoading: false, + error: null, + }, + licenses: { + data: [], + isLoading: false, + error: null, + }, + subjects: { + data: [], + isLoading: false, + error: null, + }, +}; + @State({ name: 'submitPreprint', - defaults: { - selectedProviderId: null, - createdPreprint: { - data: null, - isLoading: false, - error: null, - isSubmitting: false, - }, - fileSource: PreprintFileSource.None, - preprintFilesLinks: { - data: null, - isLoading: false, - error: null, - }, - preprintFiles: { - data: [], - isLoading: false, - error: null, - }, - availableProjects: { - data: [], - isLoading: false, - error: null, - }, - projectFiles: { - data: [], - isLoading: false, - error: null, - }, - contributors: { - data: [], - isLoading: false, - error: null, - }, - licenses: { - data: [], - isLoading: false, - error: null, - }, - }, + defaults: { ...DefaultState }, }) @Injectable() export class SubmitPreprintState { @@ -91,6 +102,7 @@ export class SubmitPreprintState { private fileService = inject(FilesService); private contributorsService = inject(PreprintContributorsService); private licensesService = inject(PreprintLicensesService); + private subjectsService = inject(PreprintSubjectsService); @Action(SetSelectedPreprintProviderId) setSelectedPreprintProviderId(ctx: StateContext, action: SetSelectedPreprintProviderId) { @@ -281,46 +293,7 @@ export class SubmitPreprintState { resetStateAndDeletePreprint(ctx: StateContext) { const state = ctx.getState(); const createdPreprintId = state.createdPreprint.data?.id; - ctx.setState({ - selectedProviderId: null, - createdPreprint: { - data: null, - isLoading: false, - error: null, - isSubmitting: false, - }, - fileSource: PreprintFileSource.None, - preprintFilesLinks: { - data: null, - isLoading: false, - error: null, - }, - preprintFiles: { - data: [], - isLoading: false, - error: null, - }, - availableProjects: { - data: [], - isLoading: false, - error: null, - }, - projectFiles: { - data: [], - isLoading: false, - error: null, - }, - contributors: { - data: [], - isLoading: false, - error: null, - }, - licenses: { - data: [], - isLoading: false, - error: null, - }, - }); + ctx.setState({ ...DefaultState }); if (createdPreprintId) { return this.preprintsService.deletePreprint(createdPreprintId); } @@ -479,6 +452,48 @@ export class SubmitPreprintState { ); } + @Action(FetchPreprintsSubjects) + fetchPreprintsSubjects(ctx: StateContext) { + const createdPreprintId = ctx.getState().createdPreprint.data!.id; + if (!createdPreprintId) return EMPTY; + + ctx.setState(patch({ subjects: patch({ isLoading: true }) })); + + return this.subjectsService.getPreprintSubjects(createdPreprintId).pipe( + tap((subjects) => { + ctx.patchState({ + subjects: { + data: subjects, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'subjects', error)) + ); + } + + @Action(UpdatePreprintsSubjects) + updatePreprintsSubjects(ctx: StateContext, { subjects }: UpdatePreprintsSubjects) { + const createdPreprintId = ctx.getState().createdPreprint.data?.id; + if (!createdPreprintId) return EMPTY; + + ctx.setState(patch({ subjects: patch({ isLoading: true }) })); + + return this.subjectsService.updatePreprintSubjects(createdPreprintId, subjects).pipe( + tap(() => { + ctx.patchState({ + subjects: { + data: subjects, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'subjects', error)) + ); + } + private handleError( ctx: StateContext, section: keyof SubmitPreprintStateModel, From 970d9b823d7a16312f88e4fee6e783e3e6b6c2fe Mon Sep 17 00:00:00 2001 From: Roma Date: Fri, 4 Jul 2025 16:10:41 +0300 Subject: [PATCH 21/42] fix(discover-state): Removed selectSnapshot that selects data within the same store --- .../preprints-discover/preprints-discover.state.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts b/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts index 65167f668..b2731569b 100644 --- a/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts +++ b/src/app/features/preprints/store/preprints-discover/preprints-discover.state.ts @@ -13,7 +13,6 @@ import { SetSortBy, } from '@osf/features/preprints/store/preprints-discover/preprints-discover.actions'; import { PreprintsDiscoverStateModel } from '@osf/features/preprints/store/preprints-discover/preprints-discover.model'; -import { PreprintsDiscoverSelectors } from '@osf/features/preprints/store/preprints-discover/preprints-discover.selectors'; import { PreprintsResourcesFiltersSelectors } from '@osf/features/preprints/store/preprints-resources-filters'; import { ResourceFiltersStateModel } from '@osf/features/search/components/resource-filters/store'; import { GetResourcesRequestTypeEnum, ResourceTab } from '@shared/enums'; @@ -53,13 +52,11 @@ export class PreprintsDiscoverState implements NgxsOnInit { if (query.type === GetResourcesRequestTypeEnum.GetResources) { const filters = this.store.selectSnapshot(PreprintsResourcesFiltersSelectors.getAllFilters); const filtersParams = addFiltersParams(filters as ResourceFiltersStateModel); - const searchText = this.store.selectSnapshot(PreprintsDiscoverSelectors.getSearchText); - const sortBy = this.store.selectSnapshot(PreprintsDiscoverSelectors.getSortBy); + const searchText = state.searchText; + const sortBy = state.sortBy; const resourceTab = ResourceTab.Preprints; const resourceTypes = getResourceTypes(resourceTab); - filtersParams['cardSearchFilter[publisher][]'] = this.store.selectSnapshot( - PreprintsDiscoverSelectors.getIri - ); + filtersParams['cardSearchFilter[publisher][]'] = state.providerIri; return this.searchService.getResources(filtersParams, searchText, sortBy, resourceTypes).pipe( tap((response) => { @@ -97,8 +94,8 @@ export class PreprintsDiscoverState implements NgxsOnInit { } @Action(GetResources) - getResources() { - if (!this.store.selectSnapshot(PreprintsDiscoverSelectors.getIri)) { + getResources(ctx: StateContext) { + if (!ctx.getState().providerIri) { return; } this.loadRequests.next({ From 6f3d20f92a749e4e1614370262a8f35682acd754 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 13:43:22 +0300 Subject: [PATCH 22/42] feat(regions): Introduced shared state for fetching regions --- .../core/constants/ngxs-states.constant.ts | 2 + .../add-project-form.component.html | 4 +- .../add-project-form.component.ts | 21 +++++----- src/app/shared/mappers/regions/index.ts | 1 + .../shared/mappers/regions/regions-mapper.ts | 11 ++++++ src/app/shared/models/regions/index.ts | 1 + .../models/regions/regions.json-api.model.ts | 9 +++++ src/app/shared/services/index.ts | 1 + src/app/shared/services/regions.service.ts | 24 ++++++++++++ src/app/shared/stores/regions/index.ts | 4 ++ .../shared/stores/regions/regions.actions.ts | 3 ++ .../shared/stores/regions/regions.model.ts | 5 +++ .../stores/regions/regions.selectors.ts | 16 ++++++++ .../shared/stores/regions/regions.state.ts | 39 +++++++++++++++++++ 14 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 src/app/shared/mappers/regions/index.ts create mode 100644 src/app/shared/mappers/regions/regions-mapper.ts create mode 100644 src/app/shared/models/regions/index.ts create mode 100644 src/app/shared/models/regions/regions.json-api.model.ts create mode 100644 src/app/shared/services/regions.service.ts create mode 100644 src/app/shared/stores/regions/index.ts create mode 100644 src/app/shared/stores/regions/regions.actions.ts create mode 100644 src/app/shared/stores/regions/regions.model.ts create mode 100644 src/app/shared/stores/regions/regions.selectors.ts create mode 100644 src/app/shared/stores/regions/regions.state.ts diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index 936f30cab..aac8027c9 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -15,6 +15,7 @@ import { NotificationSubscriptionState } from '@osf/features/settings/notificati import { ProfileSettingsState } from '@osf/features/settings/profile-settings/store/profile-settings.state'; import { AddonsState, InstitutionsState } from '@shared/stores'; import { LicensesState } from '@shared/stores/licenses'; +import { RegionsState } from '@shared/stores/regions'; export const STATES = [ AuthState, @@ -35,4 +36,5 @@ export const STATES = [ RegistrationsState, ProjectMetadataState, LicensesState, + RegionsState, ]; diff --git a/src/app/shared/components/add-project-form/add-project-form.component.html b/src/app/shared/components/add-project-form/add-project-form.component.html index a39106ee3..dfe0375a5 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.html +++ b/src/app/shared/components/add-project-form/add-project-form.component.html @@ -14,7 +14,9 @@
diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index 561f0559b..c56b293e1 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -1,4 +1,4 @@ -import { Store } from '@ngxs/store'; +import { select, Store } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; @@ -15,12 +15,12 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants'; -import { STORAGE_LOCATIONS } from '@core/constants/storage-locations.constant'; import { CreateProject, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store'; import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum'; import { ProjectForm } from '@osf/shared/models/create-project-form.model'; import { CustomValidators } from '@osf/shared/utils'; import { InstitutionsSelectors } from '@shared/stores/institutions'; +import { FetchRegions, RegionsSelectors } from '@shared/stores/regions'; import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ @@ -41,17 +41,17 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddProjectFormComponent implements OnInit { - #store = inject(Store); - protected readonly projects = this.#store.selectSignal(MyProjectsSelectors.getProjects); + private store = inject(Store); + protected readonly projects = select(MyProjectsSelectors.getProjects); protected readonly isMobile = toSignal(inject(IS_XSMALL)); protected readonly dialogRef = inject(DynamicDialogRef); protected readonly ProjectFormControls = ProjectFormControls; protected readonly hasTemplateSelected = signal(false); protected readonly isSubmitting = signal(false); - protected readonly storageLocations = STORAGE_LOCATIONS; + protected readonly storageLocations = select(RegionsSelectors.getRegions); - protected readonly affiliations = this.#store.selectSignal(InstitutionsSelectors.getUserInstitutions); + protected readonly affiliations = select(InstitutionsSelectors.getUserInstitutions); protected projectTemplateOptions = computed(() => { return this.projects().map((project) => ({ @@ -65,7 +65,7 @@ export class AddProjectFormComponent implements OnInit { nonNullable: true, validators: [CustomValidators.requiredTrimmed()], }), - [ProjectFormControls.StorageLocation]: new FormControl('us', { + [ProjectFormControls.StorageLocation]: new FormControl('', { nonNullable: true, validators: [Validators.required], }), @@ -81,13 +81,14 @@ export class AddProjectFormComponent implements OnInit { }); constructor() { + this.store.dispatch(new FetchRegions()); this.projectForm.get(ProjectFormControls.Template)?.valueChanges.subscribe((value) => { this.hasTemplateSelected.set(!!value); }); } ngOnInit(): void { - this.#store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); + this.store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); this.selectAllAffiliations(); } @@ -110,7 +111,7 @@ export class AddProjectFormComponent implements OnInit { const formValue = this.projectForm.getRawValue(); this.isSubmitting.set(true); - this.#store + this.store .dispatch( new CreateProject( formValue.title, @@ -122,7 +123,7 @@ export class AddProjectFormComponent implements OnInit { ) .subscribe({ next: () => { - this.#store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); + this.store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); this.dialogRef.close(); }, error: () => { diff --git a/src/app/shared/mappers/regions/index.ts b/src/app/shared/mappers/regions/index.ts new file mode 100644 index 000000000..241a425e0 --- /dev/null +++ b/src/app/shared/mappers/regions/index.ts @@ -0,0 +1 @@ +export * from './regions-mapper'; diff --git a/src/app/shared/mappers/regions/regions-mapper.ts b/src/app/shared/mappers/regions/regions-mapper.ts new file mode 100644 index 000000000..9af72bb54 --- /dev/null +++ b/src/app/shared/mappers/regions/regions-mapper.ts @@ -0,0 +1,11 @@ +import { IdName } from '@shared/models'; +import { RegionsResponseJsonApi } from '@shared/models/regions'; + +export class RegionsMapper { + static fromRegionsResponseJsonApi(response: RegionsResponseJsonApi): IdName[] { + return response.data.map((data) => ({ + id: data.id, + name: data.attributes.name, + })); + } +} diff --git a/src/app/shared/models/regions/index.ts b/src/app/shared/models/regions/index.ts new file mode 100644 index 000000000..a87050d50 --- /dev/null +++ b/src/app/shared/models/regions/index.ts @@ -0,0 +1 @@ +export * from './regions.json-api.model'; diff --git a/src/app/shared/models/regions/regions.json-api.model.ts b/src/app/shared/models/regions/regions.json-api.model.ts new file mode 100644 index 000000000..305c32a53 --- /dev/null +++ b/src/app/shared/models/regions/regions.json-api.model.ts @@ -0,0 +1,9 @@ +export interface RegionsResponseJsonApi { + data: { + id: string; + type: 'regions'; + attributes: { + name: string; + }; + }[]; +} diff --git a/src/app/shared/services/index.ts b/src/app/shared/services/index.ts index f3b68a7a1..cc9efe2c9 100644 --- a/src/app/shared/services/index.ts +++ b/src/app/shared/services/index.ts @@ -6,6 +6,7 @@ export { FiltersOptionsService } from './filters-options.service'; export { InstitutionsService } from './institutions.service'; export { LicensesService } from './licenses.service'; export { LoaderService } from './loader.service'; +export { RegionsService } from './regions.service'; export { ResourceCardService } from './resource-card.service'; export { SearchService } from './search.service'; export { SubjectsService } from './subjects.service'; diff --git a/src/app/shared/services/regions.service.ts b/src/app/shared/services/regions.service.ts new file mode 100644 index 000000000..4fb836929 --- /dev/null +++ b/src/app/shared/services/regions.service.ts @@ -0,0 +1,24 @@ +import { map, Observable } from 'rxjs'; + +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; + +import { RegionsMapper } from '@shared/mappers/regions'; +import { IdName } from '@shared/models'; +import { RegionsResponseJsonApi } from '@shared/models/regions'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class RegionsService { + private readonly http = inject(HttpClient); + private readonly baseUrl = environment.apiUrl; + + getAllRegions(): Observable { + return this.http + .get(`${this.baseUrl}/regions/`) + .pipe(map((regions) => RegionsMapper.fromRegionsResponseJsonApi(regions))); + } +} diff --git a/src/app/shared/stores/regions/index.ts b/src/app/shared/stores/regions/index.ts new file mode 100644 index 000000000..a3bb15b1a --- /dev/null +++ b/src/app/shared/stores/regions/index.ts @@ -0,0 +1,4 @@ +export * from './regions.actions'; +export * from './regions.model'; +export * from './regions.selectors'; +export * from './regions.state'; diff --git a/src/app/shared/stores/regions/regions.actions.ts b/src/app/shared/stores/regions/regions.actions.ts new file mode 100644 index 000000000..b6df2f670 --- /dev/null +++ b/src/app/shared/stores/regions/regions.actions.ts @@ -0,0 +1,3 @@ +export class FetchRegions { + static readonly type = '[Regions] Fetch Regions'; +} diff --git a/src/app/shared/stores/regions/regions.model.ts b/src/app/shared/stores/regions/regions.model.ts new file mode 100644 index 000000000..c7ec6e970 --- /dev/null +++ b/src/app/shared/stores/regions/regions.model.ts @@ -0,0 +1,5 @@ +import { AsyncStateModel, IdName } from '@shared/models'; + +export interface RegionsStateModel { + regions: AsyncStateModel; +} diff --git a/src/app/shared/stores/regions/regions.selectors.ts b/src/app/shared/stores/regions/regions.selectors.ts new file mode 100644 index 000000000..38453ccd2 --- /dev/null +++ b/src/app/shared/stores/regions/regions.selectors.ts @@ -0,0 +1,16 @@ +import { Selector } from '@ngxs/store'; + +import { RegionsStateModel } from './regions.model'; +import { RegionsState } from './regions.state'; + +export class RegionsSelectors { + @Selector([RegionsState]) + static getRegions(state: RegionsStateModel) { + return state.regions.data; + } + + @Selector([RegionsState]) + static areRegionsLoading(state: RegionsStateModel) { + return state.regions.isLoading; + } +} diff --git a/src/app/shared/stores/regions/regions.state.ts b/src/app/shared/stores/regions/regions.state.ts new file mode 100644 index 000000000..177b873c7 --- /dev/null +++ b/src/app/shared/stores/regions/regions.state.ts @@ -0,0 +1,39 @@ +import { Action, State, StateContext } from '@ngxs/store'; +import { patch } from '@ngxs/store/operators'; + +import { catchError, tap } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { handleSectionError } from '@core/handlers'; +import { RegionsService } from '@shared/services'; + +import { FetchRegions } from './regions.actions'; +import { RegionsStateModel } from './regions.model'; + +@State({ + name: 'regions', + defaults: { + regions: { + data: [], + isLoading: false, + error: null, + }, + }, +}) +@Injectable() +export class RegionsState { + private readonly regionsService = inject(RegionsService); + + @Action(FetchRegions) + fetchSubjects(ctx: StateContext) { + ctx.setState(patch({ regions: patch({ isLoading: true }) })); + + return this.regionsService.getAllRegions().pipe( + tap((regions) => { + ctx.setState(patch({ regions: patch({ isLoading: false, data: regions }) })); + }), + catchError((error) => handleSectionError(ctx, 'regions', error)) + ); + } +} From 91bc4467416056718abd4be6a14629adbb4a5233 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 13:47:40 +0300 Subject: [PATCH 23/42] feat(supplements): Implemented template for 'Choose existing' option --- .../features/preprints/components/index.ts | 2 + .../supplements-step.component.html | 77 +++++++++++++++++ .../supplements-step.component.scss | 21 +++++ .../supplements-step.component.spec.ts | 22 +++++ .../supplements-step.component.ts | 83 +++++++++++++++++++ src/app/features/preprints/enums/index.ts | 1 + .../enums/supplement-options.enum.ts | 5 ++ .../submit-preprint-stepper.component.html | 3 + .../submit-preprint-stepper.component.ts | 7 +- 9 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html create mode 100644 src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss create mode 100644 src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts create mode 100644 src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts create mode 100644 src/app/features/preprints/enums/supplement-options.enum.ts diff --git a/src/app/features/preprints/components/index.ts b/src/app/features/preprints/components/index.ts index 11c97beec..a5ba927a9 100644 --- a/src/app/features/preprints/components/index.ts +++ b/src/app/features/preprints/components/index.ts @@ -8,6 +8,8 @@ export { PreprintProviderFooterComponent } from './preprint-provider-footer/prep export { PreprintProviderHeroComponent } from './preprint-provider-hero/preprint-provider-hero.component'; export { PreprintServicesComponent } from './preprint-services/preprint-services.component'; export { PreprintsHelpDialogComponent } from './preprints-help-dialog/preprints-help-dialog.component'; +export { AuthorAssertionsStepComponent } from './stepper/author-assertion-step/author-assertions-step.component'; +export { SupplementsStepComponent } from './stepper/supplements-step/supplements-step.component'; export { PreprintsFilterChipsComponent } from '@osf/features/preprints/components/filters/preprints-filter-chips/preprints-filter-chips.component'; export { PreprintsResourcesComponent } from '@osf/features/preprints/components/filters/preprints-resources/preprints-resources.component'; export { PreprintsResourcesFiltersComponent } from '@osf/features/preprints/components/filters/preprints-resources-filters/preprints-resources-filters.component'; diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html new file mode 100644 index 000000000..d3e99e513 --- /dev/null +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html @@ -0,0 +1,77 @@ +

Supplements (optional)

+ +
+

Connect an OSF project to share data, code, protocols, or other supplemental materials.

+
+ +
+ + +
+ +@if (selectedSupplementOption() === SupplementOptions.ConnectExistingProject) { + @if (!selectedProjectId()) { + +
+

This will make your project public, if it is not already

+

The projects and components for which you have admin access are listed below.

+ + +
+
+ } @else { +
+

{{ selectedProjectId() }}

+ + +
+ } +} + +@if (selectedSupplementOption() === SupplementOptions.CreateNewProject) { + +} + +
+ + +
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss new file mode 100644 index 000000000..16b791219 --- /dev/null +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.scss @@ -0,0 +1,21 @@ +@use "assets/styles/mixins" as mix; + +.supplement-option-button { + --p-button-secondary-border-color: var(--grey-2); + --p-button-secondary-background: transparent; + --p-button-secondary-hover-background: var(--bg-blue-3); + --p-button-padding-y: 0.75rem; + --p-button-secondary-color: var(--dark-blue-1); + --p-button-secondary-hover-color: var(--dark-blue-1); + + &.active { + --p-button-secondary-background: var(--bg-blue-3); + } +} + +.selected-project { + @include mix.flex-center-between; + padding: mix.rem(6px) mix.rem(12px); + border-bottom: 1px solid var(--grey-2); + border-top: 1px solid var(--grey-2); +} diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts new file mode 100644 index 000000000..2cc79b093 --- /dev/null +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SupplementsStepComponent } from './supplements-step.component'; + +describe('SupplementsStepComponent', () => { + let component: SupplementsStepComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [SupplementsStepComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(SupplementsStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts new file mode 100644 index 000000000..55fa7d9e3 --- /dev/null +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts @@ -0,0 +1,83 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Select, SelectChangeEvent } from 'primeng/select'; + +import { debounceTime, distinctUntilChanged } from 'rxjs'; + +import { NgClass, TitleCasePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, output, signal } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { FormControl } from '@angular/forms'; + +import { StringOrNull } from '@core/helpers'; +import { SupplementOptions } from '@osf/features/preprints/enums'; +import { GetAvailableProjects, SubmitPreprintSelectors } from '@osf/features/preprints/store/submit-preprint'; +import { AddProjectFormComponent } from '@shared/components'; + +@Component({ + selector: 'osf-supplements-step', + imports: [Button, TitleCasePipe, NgClass, Card, Select, AddProjectFormComponent], + templateUrl: './supplements-step.component.html', + styleUrl: './supplements-step.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SupplementsStepComponent implements OnInit { + private actions = createDispatchMap({ + getAvailableProjects: GetAvailableProjects, + }); + private destroyRef = inject(DestroyRef); + + readonly SupplementOptions = SupplementOptions; + + selectedSupplementOption = signal(SupplementOptions.None); + availableProjects = select(SubmitPreprintSelectors.getAvailableProjects); + areAvailableProjectsLoading = select(SubmitPreprintSelectors.areAvailableProjectsLoading); + + selectedProjectId = signal(null); + + projectNameControl = new FormControl(null); + + nextClicked = output(); + + ngOnInit() { + this.projectNameControl.valueChanges + .pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef)) + .subscribe((projectNameOrId) => { + if (this.selectedProjectId() === projectNameOrId) { + return; + } + + this.actions.getAvailableProjects(projectNameOrId); + }); + } + + selectSupplementOption(supplementOption: SupplementOptions) { + this.selectedSupplementOption.set(supplementOption); + + if (supplementOption === SupplementOptions.ConnectExistingProject) { + this.actions.getAvailableProjects(null); + } + } + + selectProject(event: SelectChangeEvent) { + if (!(event.originalEvent instanceof PointerEvent)) { + return; + } + + this.selectedProjectId.set(event.value); + } + + disconnectProject() { + this.selectedProjectId.set(null); + } + + nextButtonClicked() { + this.nextClicked.emit(); + } + + backButtonClicked() { + //TODO + } +} diff --git a/src/app/features/preprints/enums/index.ts b/src/app/features/preprints/enums/index.ts index bf1bf51c4..6e7a24296 100644 --- a/src/app/features/preprints/enums/index.ts +++ b/src/app/features/preprints/enums/index.ts @@ -2,3 +2,4 @@ export { ApplicabilityStatus } from './applicability-status.enum'; export { PreprintFileSource } from './preprint-file-source.enum'; export { PreregLinkInfo } from './prereg-link-info.enum'; export { SubmitSteps } from './submit-steps.enum'; +export { SupplementOptions } from './supplement-options.enum'; diff --git a/src/app/features/preprints/enums/supplement-options.enum.ts b/src/app/features/preprints/enums/supplement-options.enum.ts new file mode 100644 index 000000000..0b3e50a73 --- /dev/null +++ b/src/app/features/preprints/enums/supplement-options.enum.ts @@ -0,0 +1,5 @@ +export enum SupplementOptions { + None, + ConnectExistingProject, + CreateNewProject, +} diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index 0e34f65fd..16b93e16c 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -45,6 +45,9 @@

{{ 'Add a ' + preprintProvider()!.preprintWor @case (SubmitStepsEnum.AuthorAssertions) { } + @case (SubmitStepsEnum.Supplements) { + + } @default {

No such step

} diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index aea1de1c9..e5f3f36ae 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -18,11 +18,12 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; import { + AuthorAssertionsStepComponent, FileStepComponent, MetadataStepComponent, + SupplementsStepComponent, TitleAndAbstractStepComponent, } from '@osf/features/preprints/components'; -import { AuthorAssertionsStepComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; import { SubmitSteps } from '@osf/features/preprints/enums'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; @@ -43,6 +44,8 @@ import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils'; FileStepComponent, MetadataStepComponent, AuthorAssertionsStepComponent, + SupplementsStepComponent, + AuthorAssertionsStepComponent, ], templateUrl: './submit-preprint-stepper.component.html', styleUrl: './submit-preprint-stepper.component.scss', @@ -66,7 +69,7 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - currentStep = signal(0); + currentStep = signal(4); isWeb = toSignal(inject(IS_WEB)); constructor() { From 7864e8fe83853f5737103d97d404fb9678e1c3a6 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 14:25:42 +0300 Subject: [PATCH 24/42] refactor(add-project-form): Refactored form component to be more reusable --- .../create-project-dialog.component.html | 17 +++ .../create-project-dialog.component.scss | 0 .../create-project-dialog.component.spec.ts | 22 ++++ .../create-project-dialog.component.ts | 93 ++++++++++++++ .../features/my-projects/components/index.ts | 1 + .../my-projects/my-projects.component.ts | 11 +- .../store/my-projects.selectors.ts | 5 + .../my-projects/store/my-projects.state.ts | 3 +- .../add-project-form.component.html | 40 ++---- .../add-project-form.component.ts | 114 +++++------------- 10 files changed, 180 insertions(+), 126 deletions(-) create mode 100644 src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.html create mode 100644 src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.scss create mode 100644 src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts create mode 100644 src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts create mode 100644 src/app/features/my-projects/components/index.ts diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.html b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.html new file mode 100644 index 000000000..ed5d97356 --- /dev/null +++ b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.html @@ -0,0 +1,17 @@ + +
+ + +
diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.scss b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts new file mode 100644 index 000000000..0209f9b45 --- /dev/null +++ b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CreateProjectDialogComponent } from './create-project-dialog.component'; + +describe('CreateProjectDialogComponent', () => { + let component: CreateProjectDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CreateProjectDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CreateProjectDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts new file mode 100644 index 000000000..7e876ed95 --- /dev/null +++ b/src/app/features/my-projects/components/create-project-dialog/create-project-dialog.component.ts @@ -0,0 +1,93 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Button } from 'primeng/button'; +import { DynamicDialogRef } from 'primeng/dynamicdialog'; + +import { ChangeDetectionStrategy, Component, computed, inject, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; + +import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants'; +import { CreateProject, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store'; +import { AddProjectFormComponent } from '@shared/components'; +import { ProjectFormControls } from '@shared/enums'; +import { IdName, ProjectForm } from '@shared/models'; +import { CustomValidators } from '@shared/utils'; + +@Component({ + selector: 'osf-create-project-dialog', + imports: [AddProjectFormComponent, Button, TranslatePipe], + templateUrl: './create-project-dialog.component.html', + styleUrl: './create-project-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class CreateProjectDialogComponent implements OnInit { + protected readonly dialogRef = inject(DynamicDialogRef); + + private actions = createDispatchMap({ + getMyProjects: GetMyProjects, + createProject: CreateProject, + }); + + private projects = select(MyProjectsSelectors.getProjects); + + readonly templates = computed(() => { + return this.projects().map( + (project) => + ({ + id: project.id, + name: project.title, + }) as IdName + ); + }); + readonly isProjectSubmitting = select(MyProjectsSelectors.isProjectSubmitting); + + readonly projectForm = new FormGroup({ + [ProjectFormControls.Title]: new FormControl('', { + nonNullable: true, + validators: [CustomValidators.requiredTrimmed()], + }), + [ProjectFormControls.StorageLocation]: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + [ProjectFormControls.Affiliations]: new FormControl([], { + nonNullable: true, + }), + [ProjectFormControls.Description]: new FormControl('', { + nonNullable: true, + }), + [ProjectFormControls.Template]: new FormControl('', { + nonNullable: true, + }), + }); + + ngOnInit(): void { + this.actions.getMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {}); + } + + submitForm(): void { + if (this.projectForm.invalid) { + this.projectForm.markAllAsTouched(); + return; + } + + const formValue = this.projectForm.getRawValue(); + + this.actions + .createProject( + formValue.title, + formValue.description, + formValue.template, + formValue.storageLocation, + formValue.affiliations + ) + .subscribe({ + next: () => { + this.actions.getMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {}); + this.dialogRef.close(); + }, + }); + } +} diff --git a/src/app/features/my-projects/components/index.ts b/src/app/features/my-projects/components/index.ts new file mode 100644 index 000000000..e6df1d149 --- /dev/null +++ b/src/app/features/my-projects/components/index.ts @@ -0,0 +1 @@ +export { CreateProjectDialogComponent } from './create-project-dialog/create-project-dialog.component'; diff --git a/src/app/features/my-projects/my-projects.component.ts b/src/app/features/my-projects/my-projects.component.ts index a1651ecff..6830e85ac 100644 --- a/src/app/features/my-projects/my-projects.component.ts +++ b/src/app/features/my-projects/my-projects.component.ts @@ -26,12 +26,12 @@ import { ActivatedRoute, Router } from '@angular/router'; import { MY_PROJECTS_TABLE_PARAMS } from '@osf/core/constants'; import { parseQueryFilterParams } from '@osf/core/helpers'; -import { AddProjectFormComponent, MyProjectsTableComponent, SubHeaderComponent } from '@osf/shared/components'; +import { CreateProjectDialogComponent } from '@osf/features/my-projects/components'; +import { MyProjectsTableComponent, SubHeaderComponent } from '@osf/shared/components'; import { ResourceType, SortOrder } from '@osf/shared/enums'; import { QueryParams, TableParameters, TabOption } from '@osf/shared/models'; -import { IS_MEDIUM, IS_WEB, IS_XSMALL } from '@osf/shared/utils'; +import { IS_XSMALL } from '@osf/shared/utils'; -import { FetchUserInstitutions } from '../../shared/stores/institutions'; import { CollectionsSelectors, GetBookmarksCollectionId } from '../collections/store'; import { MyProjectsItem, MyProjectsSearchFilters } from './models'; @@ -73,8 +73,6 @@ export class MyProjectsComponent implements OnInit { protected readonly defaultTabValue = 0; protected readonly isLoading = signal(false); - protected readonly isDesktop = toSignal(inject(IS_WEB)); - protected readonly isTablet = toSignal(inject(IS_MEDIUM)); protected readonly isMobile = toSignal(inject(IS_XSMALL)); protected readonly tabOptions: TabOption[] = [ { @@ -128,7 +126,6 @@ export class MyProjectsComponent implements OnInit { } ngOnInit(): void { - this.#store.dispatch(new FetchUserInstitutions()); this.#store.dispatch(new GetBookmarksCollectionId()); } @@ -339,7 +336,7 @@ export class MyProjectsComponent implements OnInit { protected createProject(): void { const dialogWidth = this.isMobile() ? '95vw' : '850px'; - this.#dialogService.open(AddProjectFormComponent, { + this.#dialogService.open(CreateProjectDialogComponent, { width: dialogWidth, focusOnShow: false, header: this.#translateService.instant('myProjects.header.createProject'), diff --git a/src/app/features/my-projects/store/my-projects.selectors.ts b/src/app/features/my-projects/store/my-projects.selectors.ts index 58cb67113..8a85cff91 100644 --- a/src/app/features/my-projects/store/my-projects.selectors.ts +++ b/src/app/features/my-projects/store/my-projects.selectors.ts @@ -11,6 +11,11 @@ export class MyProjectsSelectors { return state.projects.data; } + @Selector([MyProjectsState]) + static isProjectSubmitting(state: MyProjectsStateModel): boolean { + return state.projects.isSubmitting || false; + } + @Selector([MyProjectsState]) static getRegistrations(state: MyProjectsStateModel): MyProjectsItem[] { return state.registrations.data; diff --git a/src/app/features/my-projects/store/my-projects.state.ts b/src/app/features/my-projects/store/my-projects.state.ts index c45742f69..a0e1cc27a 100644 --- a/src/app/features/my-projects/store/my-projects.state.ts +++ b/src/app/features/my-projects/store/my-projects.state.ts @@ -234,7 +234,7 @@ export class MyProjectsState { ctx.patchState({ projects: { ...state.projects, - isLoading: true, + isSubmitting: true, }, }); @@ -246,6 +246,7 @@ export class MyProjectsState { projects: { data: [project, ...state.projects.data], isLoading: false, + isSubmitting: false, error: null, }, totalProjects: state.totalProjects + 1, diff --git a/src/app/shared/components/add-project-form/add-project-form.component.html b/src/app/shared/components/add-project-form/add-project-form.component.html index dfe0375a5..67be60576 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.html +++ b/src/app/shared/components/add-project-form/add-project-form.component.html @@ -1,5 +1,4 @@ -
- +
-
- @if (affiliations().length) {
@@ -30,21 +28,17 @@

- + /> - + />
@@ -64,7 +58,6 @@

} -
-
-
- - -
- -
diff --git a/src/app/shared/components/add-project-form/add-project-form.component.ts b/src/app/shared/components/add-project-form/add-project-form.component.ts index c56b293e1..ee4e95fbe 100644 --- a/src/app/shared/components/add-project-form/add-project-form.component.ts +++ b/src/app/shared/components/add-project-form/add-project-form.component.ts @@ -1,27 +1,21 @@ -import { select, Store } from '@ngxs/store'; +import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; import { ButtonModule } from 'primeng/button'; import { CheckboxModule } from 'primeng/checkbox'; -import { DynamicDialogRef } from 'primeng/dynamicdialog'; import { InputTextModule } from 'primeng/inputtext'; import { Select } from 'primeng/select'; import { Textarea } from 'primeng/textarea'; import { CommonModule, NgOptimizedImage } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, inject, OnInit, signal } from '@angular/core'; -import { toSignal } from '@angular/core/rxjs-interop'; -import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ChangeDetectionStrategy, Component, input, OnInit, signal } from '@angular/core'; +import { FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { MY_PROJECTS_TABLE_PARAMS } from '@core/constants/my-projects-table.constants'; -import { CreateProject, GetMyProjects, MyProjectsSelectors } from '@osf/features/my-projects/store'; -import { ProjectFormControls } from '@osf/shared/enums/create-project-form-controls.enum'; -import { ProjectForm } from '@osf/shared/models/create-project-form.model'; -import { CustomValidators } from '@osf/shared/utils'; -import { InstitutionsSelectors } from '@shared/stores/institutions'; +import { ProjectFormControls } from '@osf/shared/enums'; +import { IdName, ProjectForm } from '@osf/shared/models'; +import { FetchUserInstitutions, InstitutionsSelectors } from '@shared/stores/institutions'; import { FetchRegions, RegionsSelectors } from '@shared/stores/regions'; -import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; @Component({ selector: 'osf-add-project-form', @@ -41,94 +35,42 @@ import { IS_XSMALL } from '@shared/utils/breakpoints.tokens'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class AddProjectFormComponent implements OnInit { - private store = inject(Store); - protected readonly projects = select(MyProjectsSelectors.getProjects); - protected readonly isMobile = toSignal(inject(IS_XSMALL)); - protected readonly dialogRef = inject(DynamicDialogRef); - protected readonly ProjectFormControls = ProjectFormControls; - protected readonly hasTemplateSelected = signal(false); - protected readonly isSubmitting = signal(false); - - protected readonly storageLocations = select(RegionsSelectors.getRegions); + private actions = createDispatchMap({ + fetchUserInstitutions: FetchUserInstitutions, + fetchRegions: FetchRegions, + }); - protected readonly affiliations = select(InstitutionsSelectors.getUserInstitutions); + templates = input.required(); - protected projectTemplateOptions = computed(() => { - return this.projects().map((project) => ({ - label: project.title, - value: project.id, - })); - }); + ProjectFormControls = ProjectFormControls; - readonly projectForm = new FormGroup({ - [ProjectFormControls.Title]: new FormControl('', { - nonNullable: true, - validators: [CustomValidators.requiredTrimmed()], - }), - [ProjectFormControls.StorageLocation]: new FormControl('', { - nonNullable: true, - validators: [Validators.required], - }), - [ProjectFormControls.Affiliations]: new FormControl([], { - nonNullable: true, - }), - [ProjectFormControls.Description]: new FormControl('', { - nonNullable: true, - }), - [ProjectFormControls.Template]: new FormControl('', { - nonNullable: true, - }), - }); + hasTemplateSelected = signal(false); + isSubmitting = signal(false); + storageLocations = select(RegionsSelectors.getRegions); + areStorageLocationsLoading = select(RegionsSelectors.areRegionsLoading); + affiliations = select(InstitutionsSelectors.getUserInstitutions); - constructor() { - this.store.dispatch(new FetchRegions()); - this.projectForm.get(ProjectFormControls.Template)?.valueChanges.subscribe((value) => { - this.hasTemplateSelected.set(!!value); - }); - } + projectForm = input.required>(); ngOnInit(): void { - this.store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); + this.actions.fetchUserInstitutions(); + this.actions.fetchRegions(); this.selectAllAffiliations(); + + this.projectForm() + .get(ProjectFormControls.Template) + ?.valueChanges.subscribe((value) => { + this.hasTemplateSelected.set(!!value); + }); } selectAllAffiliations(): void { const allAffiliationValues = this.affiliations().map((aff) => aff.id); - this.projectForm.get(ProjectFormControls.Affiliations)?.setValue(allAffiliationValues); + this.projectForm().get(ProjectFormControls.Affiliations)?.setValue(allAffiliationValues); } removeAllAffiliations(): void { - this.projectForm.get(ProjectFormControls.Affiliations)?.setValue([]); - } - - submitForm(): void { - if (!this.projectForm.valid) { - this.projectForm.markAllAsTouched(); - return; - } - - const formValue = this.projectForm.getRawValue(); - this.isSubmitting.set(true); - - this.store - .dispatch( - new CreateProject( - formValue.title, - formValue.description, - formValue.template, - formValue.storageLocation, - formValue.affiliations - ) - ) - .subscribe({ - next: () => { - this.store.dispatch(new GetMyProjects(1, MY_PROJECTS_TABLE_PARAMS.rows, {})); - this.dialogRef.close(); - }, - error: () => { - this.isSubmitting.set(false); - }, - }); + this.projectForm().get(ProjectFormControls.Affiliations)?.setValue([]); } } From 05f7f65d879ba22ae08848cd0f31aaa6af1cd593 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 18:28:46 +0300 Subject: [PATCH 25/42] refactor(node-models): Reorganized shared node models --- .../models/create-project.model.ts | 25 ----------- src/app/features/my-projects/models/index.ts | 1 - .../services/my-projects.service.ts | 3 +- src/app/shared/models/index.ts | 5 +-- .../{ => nodes}/create-project-form.model.ts | 2 +- .../nodes-json-api.model.ts} | 43 +++++++++++++++++++ .../models/update-node-request.model.ts | 16 ------- 7 files changed, 47 insertions(+), 48 deletions(-) delete mode 100644 src/app/features/my-projects/models/create-project.model.ts rename src/app/shared/models/{ => nodes}/create-project-form.model.ts (86%) rename src/app/shared/models/{node-response.model.ts => nodes/nodes-json-api.model.ts} (78%) delete mode 100644 src/app/shared/models/update-node-request.model.ts diff --git a/src/app/features/my-projects/models/create-project.model.ts b/src/app/features/my-projects/models/create-project.model.ts deleted file mode 100644 index 71dc48c8e..000000000 --- a/src/app/features/my-projects/models/create-project.model.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface CreateProjectPayloadJsoApi { - data: { - type: 'nodes'; - attributes: { - title: string; - description?: string; - category: 'project'; - template_from?: string; - }; - relationships: { - region: { - data: { - type: 'regions'; - id: string; - }; - }; - affiliated_institutions?: { - data: { - type: 'institutions'; - id: string; - }[]; - }; - }; - }; -} diff --git a/src/app/features/my-projects/models/index.ts b/src/app/features/my-projects/models/index.ts index dbb1e77dc..ed1688735 100644 --- a/src/app/features/my-projects/models/index.ts +++ b/src/app/features/my-projects/models/index.ts @@ -1,4 +1,3 @@ -export * from './create-project.model'; export * from './my-projects.models'; export * from './my-projects-endpoint.type'; export * from './my-projects-search-filters.models'; diff --git a/src/app/features/my-projects/services/my-projects.service.ts b/src/app/features/my-projects/services/my-projects.service.ts index c05e86d32..9effb9750 100644 --- a/src/app/features/my-projects/services/my-projects.service.ts +++ b/src/app/features/my-projects/services/my-projects.service.ts @@ -7,11 +7,10 @@ import { JsonApiResponse } from '@core/models'; import { JsonApiService } from '@osf/core/services'; import { SparseCollectionsResponseJsonApi } from '@osf/features/collections/models'; import { ResourceType, SortOrder } from '@osf/shared/enums'; -import { NodeResponseModel, UpdateNodeRequestModel } from '@shared/models'; +import { CreateProjectPayloadJsoApi, NodeResponseModel, UpdateNodeRequestModel } from '@shared/models'; import { MyProjectsMapper } from '../mappers'; import { - CreateProjectPayloadJsoApi, EndpointType, MyProjectsItem, MyProjectsItemGetResponseJsonApi, diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 13275a443..1d5c23dd0 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -4,7 +4,6 @@ export * from './brand.model'; export * from './charts'; export * from './confirmation-options.model'; export * from './create-component-form.model'; -export * from './create-project-form.model'; export * from './file-menu-action.model'; export * from './files/file.model'; export * from './files/get-files-response.model'; @@ -19,8 +18,9 @@ export * from './license.model'; export * from './licenses-json-api.model'; export * from './metadata-field.model'; export * from './nav-item.model'; -export * from './node-response.model'; export * from './node-subject.model'; +export * from './nodes/create-project-form.model'; +export * from './nodes/nodes-json-api.model'; export * from './paginated-data.model'; export * from './query-params.model'; export * from './resource-card'; @@ -37,6 +37,5 @@ export * from './table-parameters.model'; export * from './toolbar-resource.model'; export * from './tooltip-position.model'; export * from './tutorial-step.model'; -export * from './update-node-request.model'; export * from './user'; export * from './validation-params.model'; diff --git a/src/app/shared/models/create-project-form.model.ts b/src/app/shared/models/nodes/create-project-form.model.ts similarity index 86% rename from src/app/shared/models/create-project-form.model.ts rename to src/app/shared/models/nodes/create-project-form.model.ts index d34ae3b27..9eb174c5d 100644 --- a/src/app/shared/models/create-project-form.model.ts +++ b/src/app/shared/models/nodes/create-project-form.model.ts @@ -1,6 +1,6 @@ import { FormControl } from '@angular/forms'; -import { ProjectFormControls } from '@osf/shared/enums'; +import { ProjectFormControls } from '@shared/enums'; export interface ProjectForm { [ProjectFormControls.Title]: FormControl; diff --git a/src/app/shared/models/node-response.model.ts b/src/app/shared/models/nodes/nodes-json-api.model.ts similarity index 78% rename from src/app/shared/models/node-response.model.ts rename to src/app/shared/models/nodes/nodes-json-api.model.ts index abb41506e..e760308f5 100644 --- a/src/app/shared/models/node-response.model.ts +++ b/src/app/shared/models/nodes/nodes-json-api.model.ts @@ -105,3 +105,46 @@ export interface NodeResponseModel { data: NodeData; meta: NodeMeta; } + +export interface UpdateNodeAttributes { + description?: string; + tags?: string[]; + public?: boolean; + title?: string; +} + +export interface UpdateNodeData { + type: 'nodes'; + id: string; + attributes: UpdateNodeAttributes; +} + +export interface UpdateNodeRequestModel { + data: UpdateNodeData; +} + +export interface CreateProjectPayloadJsoApi { + data: { + type: 'nodes'; + attributes: { + title: string; + description?: string; + category: 'project'; + template_from?: string; + }; + relationships: { + region: { + data: { + type: 'regions'; + id: string; + }; + }; + affiliated_institutions?: { + data: { + type: 'institutions'; + id: string; + }[]; + }; + }; + }; +} diff --git a/src/app/shared/models/update-node-request.model.ts b/src/app/shared/models/update-node-request.model.ts deleted file mode 100644 index db2ee5627..000000000 --- a/src/app/shared/models/update-node-request.model.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface UpdateNodeAttributes { - description?: string; - tags?: string[]; - public?: boolean; - title?: string; -} - -export interface UpdateNodeData { - type: 'nodes'; - id: string; - attributes: UpdateNodeAttributes; -} - -export interface UpdateNodeRequestModel { - data: UpdateNodeData; -} From 2083379b7a87bf1f014c787b08078a753d2a0258 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 19:24:41 +0300 Subject: [PATCH 26/42] feat(supplements-step): Implemented connecting project step --- .../supplements-step.component.html | 85 +++++---- .../supplements-step.component.ts | 169 ++++++++++++++++-- .../preprints/mappers/preprints.mapper.ts | 3 +- .../models/preprint-json-api.models.ts | 13 +- .../preprints/models/preprint.models.ts | 1 + src/app/features/preprints/services/index.ts | 1 + .../services/preprint-files.service.ts | 22 +-- .../services/preprints-projects.service.ts | 123 +++++++++++++ .../submit-preprint.actions.ts | 26 +++ .../submit-preprint/submit-preprint.model.ts | 1 + .../submit-preprint.selectors.ts | 10 ++ .../submit-preprint/submit-preprint.state.ts | 123 ++++++++++++- 12 files changed, 496 insertions(+), 81 deletions(-) create mode 100644 src/app/features/preprints/services/preprints-projects.service.ts diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html index d3e99e513..b942bf210 100644 --- a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html @@ -4,34 +4,33 @@

Supplements (optional)

Connect an OSF project to share data, code, protocols, or other supplemental materials.

-
- - -
+@let createdPreprintNodeId = createdPreprint()?.nodeId; +@if (!createdPreprintNodeId) { +
+ + +
-@if (selectedSupplementOption() === SupplementOptions.ConnectExistingProject) { - @if (!selectedProjectId()) { - + @if (selectedSupplementOption() === SupplementOptions.ConnectExistingProject) { +

This will make your project public, if it is not already

The projects and components for which you have admin access are listed below.

@@ -45,33 +44,41 @@

Supplements (optional)

class="w-12 md:w-6" [editable]="true" styleClass="m-t-24" + [formControl]="projectNameControl" appendTo="body" - [loading]="areAvailableProjectsLoading()" + [loading]="areAvailableProjectsLoading() || isPreprintSubmitting()" (onChange)="selectProject($event)" [showClear]="false" />
- } @else { -
-

{{ selectedProjectId() }}

- - -
+ } @else if (selectedSupplementOption() === SupplementOptions.CreateNewProject) { + + + } -} +} @else { +
+ @if (isPreprintProjectLoading()) { + + } @else { +
+

{{ preprintProject()?.name }}

-@if (selectedSupplementOption() === SupplementOptions.CreateNewProject) { - + +
+ } +
}
- +
diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts index 55fa7d9e3..4d820d84a 100644 --- a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts @@ -3,41 +3,133 @@ import { createDispatchMap, select } from '@ngxs/store'; import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { Select, SelectChangeEvent } from 'primeng/select'; +import { Skeleton } from 'primeng/skeleton'; -import { debounceTime, distinctUntilChanged } from 'rxjs'; +import { debounceTime, distinctUntilChanged, map } from 'rxjs'; import { NgClass, TitleCasePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, DestroyRef, inject, OnInit, output, signal } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FormControl } from '@angular/forms'; +import { + ChangeDetectionStrategy, + Component, + computed, + DestroyRef, + effect, + HostListener, + inject, + OnInit, + output, + signal, + untracked, +} from '@angular/core'; +import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { StringOrNull } from '@core/helpers'; import { SupplementOptions } from '@osf/features/preprints/enums'; -import { GetAvailableProjects, SubmitPreprintSelectors } from '@osf/features/preprints/store/submit-preprint'; +import { + ConnectProject, + CreateNewProject, + DisconnectProject, + FetchPreprintProject, + GetAvailableProjects, + SubmitPreprintSelectors, +} from '@osf/features/preprints/store/submit-preprint'; import { AddProjectFormComponent } from '@shared/components'; +import { ProjectFormControls } from '@shared/enums'; +import { ProjectForm } from '@shared/models'; +import { CustomConfirmationService, ToastService } from '@shared/services'; +import { CustomValidators } from '@shared/utils'; @Component({ selector: 'osf-supplements-step', - imports: [Button, TitleCasePipe, NgClass, Card, Select, AddProjectFormComponent], + imports: [Button, TitleCasePipe, NgClass, Card, Select, AddProjectFormComponent, ReactiveFormsModule, Skeleton], templateUrl: './supplements-step.component.html', styleUrl: './supplements-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) export class SupplementsStepComponent implements OnInit { + private customConfirmationService = inject(CustomConfirmationService); + private readonly toastService = inject(ToastService); private actions = createDispatchMap({ getAvailableProjects: GetAvailableProjects, + connectProject: ConnectProject, + disconnectProject: DisconnectProject, + fetchPreprintProject: FetchPreprintProject, + createNewProject: CreateNewProject, }); private destroyRef = inject(DestroyRef); readonly SupplementOptions = SupplementOptions; - selectedSupplementOption = signal(SupplementOptions.None); + createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); + isPreprintSubmitting = select(SubmitPreprintSelectors.isPreprintSubmitting); availableProjects = select(SubmitPreprintSelectors.getAvailableProjects); areAvailableProjectsLoading = select(SubmitPreprintSelectors.areAvailableProjectsLoading); + preprintProject = select(SubmitPreprintSelectors.getPreprintProject); + isPreprintProjectLoading = select(SubmitPreprintSelectors.isPreprintProjectLoading); + selectedSupplementOption = signal(SupplementOptions.None); selectedProjectId = signal(null); - projectNameControl = new FormControl(null); + readonly projectNameControl = new FormControl(null); + readonly createProjectForm = new FormGroup({ + [ProjectFormControls.Title]: new FormControl('', { + nonNullable: true, + validators: [CustomValidators.requiredTrimmed()], + }), + [ProjectFormControls.StorageLocation]: new FormControl('', { + nonNullable: true, + validators: [Validators.required], + }), + [ProjectFormControls.Affiliations]: new FormControl([], { + nonNullable: true, + }), + [ProjectFormControls.Description]: new FormControl('', { + nonNullable: true, + }), + [ProjectFormControls.Template]: new FormControl('', { + nonNullable: true, + }), + }); + + createProjectFormValid = toSignal(this.createProjectForm.statusChanges.pipe(map((status) => status === 'VALID')), { + initialValue: this.createProjectForm.valid, + }); + + isNextButtonDisabled = computed(() => { + if (this.createdPreprint()?.nodeId) { + return false; + } + + switch (this.selectedSupplementOption()) { + case SupplementOptions.None: + return true; + case SupplementOptions.ConnectExistingProject: + return !this.createdPreprint()?.nodeId; + case SupplementOptions.CreateNewProject: + return !this.createProjectFormValid(); + default: + return false; + } + }); + + constructor() { + effect(() => { + const preprint = this.createdPreprint(); + if (!preprint?.nodeId) { + return; + } + + untracked(() => { + const preprintProject = this.preprintProject(); + if (preprint.nodeId === preprintProject?.id) { + return; + } + }); + + this.actions.fetchPreprintProject(); + }); + } nextClicked = output(); @@ -56,28 +148,77 @@ export class SupplementsStepComponent implements OnInit { selectSupplementOption(supplementOption: SupplementOptions) { this.selectedSupplementOption.set(supplementOption); - if (supplementOption === SupplementOptions.ConnectExistingProject) { - this.actions.getAvailableProjects(null); + if (supplementOption === SupplementOptions.CreateNewProject) { + this.createProjectForm.reset(); } + + this.actions.getAvailableProjects(null); } selectProject(event: SelectChangeEvent) { if (!(event.originalEvent instanceof PointerEvent)) { return; } - this.selectedProjectId.set(event.value); + + this.actions.connectProject(event.value).subscribe({ + complete: () => { + this.toastService.showSuccess('Project connected successfully'); + }, + }); } disconnectProject() { - this.selectedProjectId.set(null); + this.customConfirmationService.confirmDelete({ + headerKey: 'Disconnect supplemental material', + messageKey: + 'This will disconnect the selected project. You can select new supplemental material or re-add the same supplemental material at a later date.', + onConfirm: () => { + this.actions.disconnectProject().subscribe({ + complete: () => { + this.selectedProjectId.set(null); + this.toastService.showSuccess('Project disconnected successfully'); + }, + }); + }, + }); + } + + submitCreateProjectForm() { + if (this.createProjectForm.invalid) { + return; + } + + const rawData = this.createProjectForm.getRawValue(); + + this.actions + .createNewProject( + rawData.title, + rawData.description, + rawData.template, + rawData.storageLocation, + rawData.affiliations + ) + .subscribe({ + complete: () => { + this.toastService.showSuccess('Project created successfully'); + this.nextClicked.emit(); + }, + }); } nextButtonClicked() { + if (this.selectedSupplementOption() === SupplementOptions.CreateNewProject) { + this.submitCreateProjectForm(); + return; + } + this.nextClicked.emit(); } - backButtonClicked() { - //TODO + @HostListener('window:beforeunload', ['$event']) + public onBeforeUnload($event: BeforeUnloadEvent): boolean { + $event.preventDefault(); + return false; } } diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 2661e1388..5add26b6d 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -37,7 +37,8 @@ export class PreprintsMapper { isPublic: response.attributes.public, version: response.attributes.version, isLatestVersion: response.attributes.is_latest_version, - primaryFileId: response.relationships.primary_file?.links?.related?.href || null, + primaryFileId: response.relationships.primary_file?.data?.id || null, + nodeId: response.relationships.node?.data?.id, licenseId: response.relationships.license?.data?.id || null, licenseOptions: response.attributes.license_record ? { diff --git a/src/app/features/preprints/models/preprint-json-api.models.ts b/src/app/features/preprints/models/preprint-json-api.models.ts index 44ae1cd64..f09523304 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -36,10 +36,9 @@ export interface PreprintJsonApi { export interface PreprintsRelationshipsJsonApi { primary_file: { - links: { - related: { - href: string; - }; + data: { + id: string; + type: 'files'; }; }; license: { @@ -48,4 +47,10 @@ export interface PreprintsRelationshipsJsonApi { type: 'licenses'; }; }; + node: { + data: { + id: string; + type: 'nodes'; + }; + }; } diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index 7f797cd77..d791c2d23 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -16,6 +16,7 @@ export interface Preprint { isPublic: boolean; version: number; isLatestVersion: boolean; + nodeId: StringOrNull; primaryFileId: StringOrNull; licenseId: StringOrNull; licenseOptions: LicenseOptions | null; diff --git a/src/app/features/preprints/services/index.ts b/src/app/features/preprints/services/index.ts index da9019e74..1bf75058c 100644 --- a/src/app/features/preprints/services/index.ts +++ b/src/app/features/preprints/services/index.ts @@ -4,4 +4,5 @@ export { PreprintLicensesService } from './preprint-licenses.service'; export { PreprintProvidersService } from './preprint-providers.service'; export { PreprintSubjectsService } from './preprint-subjects.service'; export { PreprintsService } from './preprints.service'; +export { PreprintsProjectsService } from './preprints-projects.service'; export { PreprintsFiltersOptionsService } from './preprints-resource-filters.service'; diff --git a/src/app/features/preprints/services/preprint-files.service.ts b/src/app/features/preprints/services/preprint-files.service.ts index 141d255b8..5a8effc95 100644 --- a/src/app/features/preprints/services/preprint-files.service.ts +++ b/src/app/features/preprints/services/preprint-files.service.ts @@ -2,7 +2,6 @@ import { map, Observable, switchMap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { Primitive, StringOrNull } from '@core/helpers'; import { JsonApiService } from '@core/services'; import { ApiData, JsonApiResponse } from '@osf/core/models'; import { PreprintsMapper } from '@osf/features/preprints/mappers'; @@ -12,7 +11,7 @@ import { PreprintJsonApi, PreprintsRelationshipsJsonApi, } from '@osf/features/preprints/models'; -import { GetFileResponse, GetFilesResponse, IdName, NodeData, OsfFile } from '@osf/shared/models'; +import { GetFileResponse, GetFilesResponse, OsfFile } from '@osf/shared/models'; import { FilesService } from '@shared/services'; import { environment } from 'src/environments/environment'; @@ -61,25 +60,6 @@ export class PreprintFilesService { ); } - getAvailableProjects(searchTerm: StringOrNull): Observable { - const params: Record = {}; - params['page'] = 1; - if (searchTerm) { - params['filter[title]'] = searchTerm; - } - - return this.jsonApiService - .get>(`${environment.apiUrl}/users/me/nodes/`, params) - .pipe( - map((response) => { - return response.data.map((item) => ({ - id: item.id, - name: item.attributes.title, - })); - }) - ); - } - getProjectFiles(projectId: string): Observable { return this.jsonApiService.get(`${environment.apiUrl}/nodes/${projectId}/files/`).pipe( switchMap((response: GetFilesResponse) => { diff --git a/src/app/features/preprints/services/preprints-projects.service.ts b/src/app/features/preprints/services/preprints-projects.service.ts new file mode 100644 index 000000000..4c47f580f --- /dev/null +++ b/src/app/features/preprints/services/preprints-projects.service.ts @@ -0,0 +1,123 @@ +import { map, Observable } from 'rxjs'; + +import { inject, Injectable } from '@angular/core'; + +import { Primitive, StringOrNull } from '@core/helpers'; +import { JsonApiService } from '@core/services'; +import { ApiData, JsonApiResponse } from '@osf/core/models'; +import { PreprintsMapper } from '@osf/features/preprints/mappers'; +import { Preprint, PreprintJsonApi, PreprintsRelationshipsJsonApi } from '@osf/features/preprints/models'; +import { CreateProjectPayloadJsoApi, IdName, NodeData } from '@osf/shared/models'; + +import { environment } from 'src/environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class PreprintsProjectsService { + private jsonApiService = inject(JsonApiService); + + getAvailableProjects(searchTerm: StringOrNull): Observable { + const params: Record = {}; + params['page'] = 1; + if (searchTerm) { + params['filter[title]'] = searchTerm; + } + + return this.jsonApiService + .get>(`${environment.apiUrl}/users/me/nodes/`, params) + .pipe( + map((response) => { + return response.data.map((item) => ({ + id: item.id, + name: item.attributes.title, + })); + }) + ); + } + + getProjectById(projectId: string): Observable { + return this.jsonApiService.get>(`${environment.apiUrl}/nodes/${projectId}/`).pipe( + map((response) => { + return { + id: response.data.id, + name: response.data.attributes.title, + }; + }) + ); + } + + removePreprintProjectRelationship(preprintId: string) { + return this.jsonApiService.patch(`${environment.apiUrl}/preprints/${preprintId}/relationships/node/`, { + data: [], + }); + } + + updatePreprintProjectRelationship(preprintId: string, projectId: string): Observable { + return this.jsonApiService + .patch>( + `${environment.apiUrl}/preprints/${preprintId}/`, + { + data: { + type: 'preprints', + id: preprintId, + attributes: {}, + relationships: { + node: { + data: { + type: 'nodes', + id: projectId, + }, + }, + }, + }, + } + ) + .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response))); + } + + createProject( + title: string, + description: string, + templateFrom: string, + regionId: string, + affiliationsId: string[] + ): Observable { + const payload: CreateProjectPayloadJsoApi = { + data: { + type: 'nodes', + attributes: { + title, + ...(description && { description }), + category: 'project', + ...(templateFrom && { template_from: templateFrom }), + }, + relationships: { + region: { + data: { + type: 'regions', + id: regionId, + }, + }, + ...(affiliationsId.length > 0 && { + affiliated_institutions: { + data: affiliationsId.map((id) => ({ + type: 'institutions', + id, + })), + }, + }), + }, + }, + }; + + return this.jsonApiService.post>(`${environment.apiUrl}/nodes/`, payload).pipe( + map((response) => { + return { + id: response.data.id, + name: response.data.attributes.title, + }; + }) + ); + } +} diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts index 6b2b90729..f326299c5 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts @@ -124,6 +124,32 @@ export class UpdatePreprintsSubjects { constructor(public subjects: Subject[]) {} } +export class DisconnectProject { + static readonly type = '[Submit Preprint] Disconnect Preprint Project'; +} + +export class ConnectProject { + static readonly type = '[Submit Preprint] Connect Preprint Project'; + + constructor(public projectId: string) {} +} + +export class FetchPreprintProject { + static readonly type = '[Submit Preprint] Fetch Preprint Project'; +} + +export class CreateNewProject { + static readonly type = '[Submit Preprint] Create Project'; + + constructor( + public title: string, + public description: string, + public templateFrom: string, + public regionId: string, + public affiliationsId: string[] + ) {} +} + export class ResetStateAndDeletePreprint { static readonly type = '[Submit Preprint] Reset State And Delete Preprint'; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts index 85c7f0676..8b7dccbb4 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts @@ -16,4 +16,5 @@ export interface SubmitPreprintStateModel { contributors: AsyncStateModel; licenses: AsyncStateModel; subjects: AsyncStateModel; + preprintProject: AsyncStateModel; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts index 565ede534..83d58d99a 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts @@ -82,4 +82,14 @@ export class SubmitPreprintSelectors { static isSubjectsUpdating(state: SubmitPreprintStateModel) { return state.subjects.isLoading; } + + @Selector([SubmitPreprintState]) + static getPreprintProject(state: SubmitPreprintStateModel) { + return state.preprintProject.data; + } + + @Selector([SubmitPreprintState]) + static isPreprintProjectLoading(state: SubmitPreprintStateModel) { + return state.preprintProject.isLoading; + } } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index bc1b00dac..5855f6d4d 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -1,7 +1,7 @@ import { Action, State, StateContext } from '@ngxs/store'; import { insertItem, patch, removeItem, updateItem } from '@ngxs/store/operators'; -import { EMPTY, filter, switchMap, tap, throwError } from 'rxjs'; +import { EMPTY, filter, forkJoin, of, switchMap, tap, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { HttpEventType } from '@angular/common/http'; @@ -14,6 +14,7 @@ import { PreprintContributorsService, PreprintFilesService, PreprintLicensesService, + PreprintsProjectsService, PreprintsService, PreprintSubjectsService, } from '@osf/features/preprints/services'; @@ -22,11 +23,15 @@ import { FilesService } from '@shared/services'; import { AddContributor, + ConnectProject, CopyFileFromProject, + CreateNewProject, CreatePreprint, DeleteContributor, + DisconnectProject, FetchContributors, FetchLicenses, + FetchPreprintProject, FetchPreprintsSubjects, GetAvailableProjects, GetPreprintFiles, @@ -89,6 +94,11 @@ const DefaultState: SubmitPreprintStateModel = { isLoading: false, error: null, }, + preprintProject: { + data: null, + isLoading: false, + error: null, + }, }; @State({ @@ -103,6 +113,7 @@ export class SubmitPreprintState { private contributorsService = inject(PreprintContributorsService); private licensesService = inject(PreprintLicensesService); private subjectsService = inject(PreprintSubjectsService); + private preprintProjectsService = inject(PreprintsProjectsService); @Action(SetSelectedPreprintProviderId) setSelectedPreprintProviderId(ctx: StateContext, action: SetSelectedPreprintProviderId) { @@ -227,7 +238,7 @@ export class SubmitPreprintState { getAvailableProjects(ctx: StateContext, action: GetAvailableProjects) { ctx.setState(patch({ availableProjects: patch({ isLoading: true }) })); - return this.preprintFilesService.getAvailableProjects(action.searchTerm).pipe( + return this.preprintProjectsService.getAvailableProjects(action.searchTerm).pipe( tap((projects) => { ctx.setState( patch({ @@ -494,6 +505,114 @@ export class SubmitPreprintState { ); } + @Action(DisconnectProject) + disconnectProject(ctx: StateContext) { + const createdPreprintId = ctx.getState().createdPreprint.data?.id; + if (!createdPreprintId) return EMPTY; + + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) })); + + return this.preprintProjectsService.removePreprintProjectRelationship(createdPreprintId).pipe( + tap(() => { + ctx.patchState({ + createdPreprint: { + ...ctx.getState().createdPreprint, + data: { + ...ctx.getState().createdPreprint.data!, + nodeId: null, + }, + isSubmitting: false, + }, + preprintProject: { + data: null, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'createdPreprint', error)) + ); + } + + @Action(ConnectProject) + connectProject(ctx: StateContext, { projectId }: ConnectProject) { + const createdPreprintId = ctx.getState().createdPreprint.data?.id; + if (!createdPreprintId) return EMPTY; + + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) })); + + return this.preprintProjectsService.updatePreprintProjectRelationship(createdPreprintId, projectId).pipe( + tap((preprint) => { + ctx.patchState({ + createdPreprint: { + data: preprint, + isLoading: false, + isSubmitting: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'createdPreprint', error)) + ); + } + + @Action(FetchPreprintProject) + fetchPreprintProject(ctx: StateContext) { + const preprintProjectId = ctx.getState().createdPreprint.data?.nodeId; + if (!preprintProjectId) return EMPTY; + + ctx.setState(patch({ preprintProject: patch({ isLoading: true }) })); + + return this.preprintProjectsService.getProjectById(preprintProjectId).pipe( + tap((project) => { + ctx.patchState({ + preprintProject: { + data: project, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => handleSectionError(ctx, 'preprintProject', error)) + ); + } + + @Action(CreateNewProject) + createNewProject(ctx: StateContext, action: CreateNewProject) { + const createdPreprintId = ctx.getState().createdPreprint.data!.id; + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) })); + ctx.setState(patch({ preprintProject: patch({ isLoading: true }) })); + + return this.preprintProjectsService + .createProject(action.title, action.description, action.templateFrom, action.regionId, action.affiliationsId) + .pipe( + switchMap((project) => + forkJoin([ + of(project), + this.preprintProjectsService.updatePreprintProjectRelationship(createdPreprintId, project.id), + ]) + ), + tap(([project, preprint]) => { + ctx.patchState({ + createdPreprint: { + ...ctx.getState().createdPreprint, + data: { + ...ctx.getState().createdPreprint.data!, + nodeId: preprint.nodeId, + }, + isSubmitting: false, + }, + preprintProject: { + data: project, + isLoading: false, + error: null, + }, + }); + }), + catchError((error) => this.handleError(ctx, 'preprintProject', error)) + ); + } + private handleError( ctx: StateContext, section: keyof SubmitPreprintStateModel, From deae84c23c90f8f2028339ab7a6da05fd68f9d83 Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 19:36:52 +0300 Subject: [PATCH 27/42] fix(conflict): Fixed things after merge conflict --- .../features/preprints/preprints.routes.ts | 2 - .../submit-preprint.actions.ts | 23 ----- .../submit-preprint/submit-preprint.state.ts | 95 +------------------ src/app/shared/models/index.ts | 1 - 4 files changed, 1 insertion(+), 120 deletions(-) diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index 49529d677..72323b68e 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -9,7 +9,6 @@ import { PreprintsDiscoverState } from '@osf/features/preprints/store/preprints- import { PreprintsResourcesFiltersState } from '@osf/features/preprints/store/preprints-resources-filters'; import { PreprintsResourcesFiltersOptionsState } from '@osf/features/preprints/store/preprints-resources-filters-options'; import { SubmitPreprintState } from '@osf/features/preprints/store/submit-preprint'; -import { ContributorsState } from '@shared/components/contributors/store'; import { SubjectsState } from '@shared/stores'; import { SUBJECTS_SERVICE } from '@shared/tokens/subjects.token'; @@ -24,7 +23,6 @@ export const preprintsRoutes: Routes = [ PreprintsResourcesFiltersState, PreprintsResourcesFiltersOptionsState, SubmitPreprintState, - ContributorsState, SubjectsState, ]), { diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts index f326299c5..f379ce1bb 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts @@ -1,7 +1,6 @@ import { StringOrNull } from '@core/helpers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint } from '@osf/features/preprints/models'; -import { ContributorAddModel, ContributorModel } from '@shared/components/contributors/models'; import { LicenseOptions, OsfFile, Subject } from '@shared/models'; export class SetSelectedPreprintProviderId { @@ -79,28 +78,6 @@ export class GetProjectFilesByLink { constructor(public filesLink: string) {} } -export class FetchContributors { - static readonly type = '[Submit Preprint] Fetch Contributors'; -} - -export class AddContributor { - static readonly type = '[Submit Preprint] Add Contributor'; - - constructor(public contributor: ContributorAddModel) {} -} - -export class UpdateContributor { - static readonly type = '[Submit Preprint] Update Contributor'; - - constructor(public contributor: ContributorModel) {} -} - -export class DeleteContributor { - static readonly type = '[Submit Preprint] Delete Contributor'; - - constructor(public userId: string) {} -} - export class FetchLicenses { static readonly type = '[Submit Preprint] Fetch Licenses'; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 5855f6d4d..1cfd51b7c 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -1,5 +1,5 @@ import { Action, State, StateContext } from '@ngxs/store'; -import { insertItem, patch, removeItem, updateItem } from '@ngxs/store/operators'; +import { patch } from '@ngxs/store/operators'; import { EMPTY, filter, forkJoin, of, switchMap, tap, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -11,7 +11,6 @@ import { handleSectionError } from '@core/handlers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; import { Preprint } from '@osf/features/preprints/models'; import { - PreprintContributorsService, PreprintFilesService, PreprintLicensesService, PreprintsProjectsService, @@ -22,14 +21,11 @@ import { OsfFile } from '@shared/models'; import { FilesService } from '@shared/services'; import { - AddContributor, ConnectProject, CopyFileFromProject, CreateNewProject, CreatePreprint, - DeleteContributor, DisconnectProject, - FetchContributors, FetchLicenses, FetchPreprintProject, FetchPreprintsSubjects, @@ -44,7 +40,6 @@ import { SetSelectedPreprintFileSource, SetSelectedPreprintProviderId, SubmitPreprintStateModel, - UpdateContributor, UpdatePreprint, UpdatePreprintsSubjects, UploadFile, @@ -79,11 +74,6 @@ const DefaultState: SubmitPreprintStateModel = { isLoading: false, error: null, }, - contributors: { - data: [], - isLoading: false, - error: null, - }, licenses: { data: [], isLoading: false, @@ -110,7 +100,6 @@ export class SubmitPreprintState { private preprintsService = inject(PreprintsService); private preprintFilesService = inject(PreprintFilesService); private fileService = inject(FilesService); - private contributorsService = inject(PreprintContributorsService); private licensesService = inject(PreprintLicensesService); private subjectsService = inject(PreprintSubjectsService); private preprintProjectsService = inject(PreprintsProjectsService); @@ -354,88 +343,6 @@ export class SubmitPreprintState { ); } - @Action(FetchContributors) - fetchContributors(ctx: StateContext) { - const createdPreprint = ctx.getState().createdPreprint.data; - if (!createdPreprint) { - return; - } - - ctx.setState(patch({ contributors: patch({ isLoading: true }) })); - - return this.contributorsService.getContributors(createdPreprint.id).pipe( - tap((contributors) => { - ctx.setState(patch({ contributors: patch({ isLoading: false, data: contributors }) })); - }), - catchError((error) => this.handleError(ctx, 'contributors', error)) - ); - } - - @Action(AddContributor) - addContributor(ctx: StateContext, action: AddContributor) { - const createdPreprint = ctx.getState().createdPreprint.data; - if (!createdPreprint) { - return; - } - - ctx.setState(patch({ contributors: patch({ isLoading: true }) })); - - return this.contributorsService.addContributor(createdPreprint.id, action.contributor).pipe( - tap((contributor) => { - ctx.setState(patch({ contributors: patch({ isLoading: false, data: insertItem(contributor) }) })); - }), - catchError((error) => this.handleError(ctx, 'contributors', error)) - ); - } - - @Action(UpdateContributor) - updateContributor(ctx: StateContext, action: UpdateContributor) { - const createdPreprint = ctx.getState().createdPreprint.data; - if (!createdPreprint) { - return; - } - - ctx.setState(patch({ contributors: patch({ isLoading: true }) })); - - return this.contributorsService.updateContributor(createdPreprint.id, action.contributor).pipe( - tap((contributor) => { - ctx.setState( - patch({ - contributors: patch({ - isLoading: false, - data: updateItem((item) => item.id === action.contributor.id, contributor), - }), - }) - ); - }), - catchError((error) => this.handleError(ctx, 'contributors', error)) - ); - } - - @Action(DeleteContributor) - deleteContributor(ctx: StateContext, action: DeleteContributor) { - const createdPreprint = ctx.getState().createdPreprint.data; - if (!createdPreprint) { - return; - } - - ctx.setState(patch({ contributors: patch({ isLoading: true }) })); - - return this.contributorsService.deleteContributor(createdPreprint.id, action.userId).pipe( - tap(() => { - ctx.setState( - patch({ - contributors: patch({ - isLoading: false, - data: removeItem((item) => action.userId === item.userId), - }), - }) - ); - }), - catchError((error) => this.handleError(ctx, 'contributors', error)) - ); - } - @Action(FetchLicenses) fetchLicenses(ctx: StateContext) { const providerId = ctx.getState().selectedProviderId; diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 548e0cd5f..08e4a449a 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -18,7 +18,6 @@ export * from './license.model'; export * from './license.model'; export * from './licenses-json-api.model'; export * from './metadata-field.model'; -export * from './nav-item.model'; export * from './node-subject.model'; export * from './nodes/create-project-form.model'; export * from './nodes/nodes-json-api.model'; From 39c0b455c13c1b1ada26aa742c44c5e1d147270e Mon Sep 17 00:00:00 2001 From: Roma Date: Mon, 7 Jul 2025 19:52:48 +0300 Subject: [PATCH 28/42] fix(conflict): Fixed things after merge conflict --- .../submit-preprint-stepper.component.ts | 2 +- src/app/features/preprints/preprints.routes.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index e5f3f36ae..b1a768a2c 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -69,7 +69,7 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - currentStep = signal(4); + currentStep = signal(0); isWeb = toSignal(inject(IS_WEB)); constructor() { diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index 72323b68e..f2caae2b0 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -9,7 +9,7 @@ import { PreprintsDiscoverState } from '@osf/features/preprints/store/preprints- import { PreprintsResourcesFiltersState } from '@osf/features/preprints/store/preprints-resources-filters'; import { PreprintsResourcesFiltersOptionsState } from '@osf/features/preprints/store/preprints-resources-filters-options'; import { SubmitPreprintState } from '@osf/features/preprints/store/submit-preprint'; -import { SubjectsState } from '@shared/stores'; +import { ContributorsState, SubjectsState } from '@shared/stores'; import { SUBJECTS_SERVICE } from '@shared/tokens/subjects.token'; export const preprintsRoutes: Routes = [ @@ -23,6 +23,7 @@ export const preprintsRoutes: Routes = [ PreprintsResourcesFiltersState, PreprintsResourcesFiltersOptionsState, SubmitPreprintState, + ContributorsState, SubjectsState, ]), { From dc24f68c0223a6be86fbf4abf1767e38fe833efb Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 11:17:06 +0300 Subject: [PATCH 29/42] fix(registries-subjects): Use constant for OSF provider ID in fetchSubjects --- .../registries-subjects/registries-subjects.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts index 014aa2b5d..0f1fabb40 100644 --- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts +++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts @@ -22,6 +22,7 @@ import { FetchChildrenSubjects, FetchSubjects } from '@osf/shared/stores'; export class RegistriesSubjectsComponent { private readonly route = inject(ActivatedRoute); private readonly draftId = this.route.snapshot.params['id']; + private readonly OSF_PROVIDER_ID = 'osf'; protected selectedSubjects = select(RegistriesSelectors.getSelectedSubjects); protected isSubjectsUpdating = select(RegistriesSelectors.isSubjectsUpdating); @@ -34,7 +35,7 @@ export class RegistriesSubjectsComponent { }); constructor() { - this.actions.fetchSubjects('osf'); + this.actions.fetchSubjects(this.OSF_PROVIDER_ID); this.actions.fetchRegistrationSubjects(this.draftId); } From 587f63994c6c8b430746ae0636cf0819a6e36286 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 11:33:29 +0300 Subject: [PATCH 30/42] fix(submit-preprint): Fixes after merge conflict --- .../submit-preprint-stepper.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index b068de27a..10e735f39 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -46,7 +46,7 @@

{{ 'Add a ' + preprintProvider()!.preprintWor } @case (SubmitStepsEnum.Supplements) { - + } @default {

No such step

From a489aa4ffdfa408c612d13bf0b7743cdbc1e71f1 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 19:19:29 +0300 Subject: [PATCH 31/42] fix(author-assertions): Fixed link info dropdown options --- .../author-assertions-step.component.ts | 7 ++----- src/app/features/preprints/constants/index.ts | 1 + .../constants/prereg-link-options.const.ts | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 src/app/features/preprints/constants/prereg-link-options.const.ts diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts index 9af511445..d7fc71472 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.ts @@ -26,7 +26,7 @@ import { import { StringOrNull } from '@core/helpers'; import { ArrayInputComponent } from '@osf/features/preprints/components/stepper/author-assertion-step/array-input/array-input.component'; -import { formInputLimits } from '@osf/features/preprints/constants'; +import { formInputLimits, preregLinksOptions } from '@osf/features/preprints/constants'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { SubmitPreprintSelectors, UpdatePreprint } from '@osf/features/preprints/store/submit-preprint'; import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; @@ -63,10 +63,7 @@ export class AuthorAssertionsStepComponent { readonly ApplicabilityStatus = ApplicabilityStatus; readonly inputLimits = formInputLimits; readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; - readonly preregLinkOptions = Object.entries(PreregLinkInfo).map(([key, value]) => ({ - label: key, - value, - })); + readonly preregLinkOptions = preregLinksOptions; readonly linkValidators = [CustomValidators.linkValidator(), CustomValidators.requiredTrimmed()]; createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); diff --git a/src/app/features/preprints/constants/index.ts b/src/app/features/preprints/constants/index.ts index dd00ac636..0dd479b4e 100644 --- a/src/app/features/preprints/constants/index.ts +++ b/src/app/features/preprints/constants/index.ts @@ -1,2 +1,3 @@ export * from './form-input-limits.const'; +export * from './prereg-link-options.const'; export * from './submit-preprint-steps.const'; diff --git a/src/app/features/preprints/constants/prereg-link-options.const.ts b/src/app/features/preprints/constants/prereg-link-options.const.ts new file mode 100644 index 000000000..680a653b4 --- /dev/null +++ b/src/app/features/preprints/constants/prereg-link-options.const.ts @@ -0,0 +1,17 @@ +import { PreregLinkInfo } from '@osf/features/preprints/enums'; +import { SelectOption } from '@shared/models'; + +export const preregLinksOptions: SelectOption[] = [ + { + label: 'Analysis Plan', + value: PreregLinkInfo.Analysis, + }, + { + label: 'Study Design', + value: PreregLinkInfo.Designs, + }, + { + label: 'Both', + value: PreregLinkInfo.Both, + }, +]; From 2b017f6c67475d7301198abdebcc923393482f6d Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 20:30:14 +0300 Subject: [PATCH 32/42] fix(file-step): Using preprint word from provider on UI --- .../components/stepper/file-step/file-step.component.html | 2 +- .../components/stepper/file-step/file-step.component.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.html b/src/app/features/preprints/components/stepper/file-step/file-step.component.html index aa47143d0..ddc5a7f97 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.html +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.html @@ -1,7 +1,7 @@

File

-

Please Upload your Preprint

+

Please Upload your {{ provider()?.preprintWord | titlecase }}

Note: You cannot switch options once a file is attached.

diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts index 315e832d2..327530771 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts @@ -17,6 +17,7 @@ import { DestroyRef, HostListener, inject, + input, OnInit, output, signal, @@ -26,6 +27,7 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { StringOrNull } from '@core/helpers'; import { PreprintFileSource } from '@osf/features/preprints/enums'; +import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { CopyFileFromProject, GetAvailableProjects, @@ -77,6 +79,7 @@ export class FileStepComponent implements OnInit { readonly PreprintFileSource = PreprintFileSource; + provider = input.required(); createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); providerId = select(SubmitPreprintSelectors.getSelectedProviderId); selectedFileSource = select(SubmitPreprintSelectors.getSelectedFileSource); From 0675cb59ed7c684f97e0ada497a23cd36402dfae Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 20:33:55 +0300 Subject: [PATCH 33/42] feat(review-step): Implemented UI for review step --- .../review-step/review-step.component.html | 209 ++++++++++++++++++ .../review-step/review-step.component.scss | 7 + .../review-step/review-step.component.spec.ts | 22 ++ .../review-step/review-step.component.ts | 62 ++++++ .../constants/submit-preprint-steps.const.ts | 12 +- .../mappers/preprint-providers.mapper.ts | 3 + .../preprint-provider-json-api.models.ts | 2 + .../models/preprint-provider.models.ts | 3 + .../submit-preprint-stepper.component.html | 14 +- .../submit-preprint-stepper.component.ts | 27 ++- .../submit-preprint.selectors.ts | 5 + 11 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 src/app/features/preprints/components/stepper/review-step/review-step.component.html create mode 100644 src/app/features/preprints/components/stepper/review-step/review-step.component.scss create mode 100644 src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts create mode 100644 src/app/features/preprints/components/stepper/review-step/review-step.component.ts diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html new file mode 100644 index 000000000..c85b00f5c --- /dev/null +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html @@ -0,0 +1,209 @@ +

Consent to publish

+

+ By submitting this preprint you confirm that all contributors agree with sharing it and that you have the right to + share this preprint. +

+

+ {{ provider()?.name }} uses {{ provider()?.reviewsWorkflow }}. If your preprint is accepted, it will be assigned a DOI + and become publicly accessible via OSF. The preprint file cannot be deleted but it can be updated or modified. +

+

+ You can read more about + OSF preprints moderation policies on the OSF support + center. +

+ + +
+

Title and Abstract

+ +
+

{{ provider()?.preprintWord | titlecase }} Service

+
+ Provider logo +

{{ provider()?.name }}

+
+
+ +
+

Title

+

{{ createdPreprint()!.title }}

+
+ +
+

Abstract

+ +
+
+
+ + +
+

Metadata

+ +
+

Contributors

+ +
+ @for (contributor of bibliographicContributors(); track contributor.id) { +
+ {{ contributor.fullName }} + {{ $last ? '' : ',' }} +
+ } +
+
+ + @if (affiliatedInstitutions().length) { +
+

Affiliated Institutions

+ +
+ @for (institution of affiliatedInstitutions(); track institution.id) { + Institution logo + } +
+
+ } + +
+

License

+ +
{{ license()?.name }}
+
+ +
+

Publication DOI

+ + + {{ 'https://doi.org/' + createdPreprint()?.doi }} + +
+ +
+

Subjects

+ +
+ @for (subject of subjects(); track subject.id) { + + } +
+
+ +
+

Tags

+ +
+ @for (tag of createdPreprint()?.tags; track tag) { + + } @empty { +

None

+ } +
+
+ +
+

Publication Date

+ + @if (createdPreprint()?.originalPublicationDate) { + {{ createdPreprint()?.originalPublicationDate | date: 'MMM d, y, h:mm a' }} + } @else { +

Not Applicable

+ } +
+ +
+

Publication Citation

+ + @if (createdPreprint()?.customPublicationCitation) { + {{ createdPreprint()?.customPublicationCitation }} + } @else { +

Not Applicable

+ } +
+
+
+ +@if (provider()?.assertionsEnabled) { + +
+

Author Assertions

+ +
+

Conflict of Interest

+ + @if (!createdPreprint()?.hasCoi) { +

Author asserted no Conflict of Interest.

+ } @else { + {{ createdPreprint()?.coiStatement }} + } +
+ +
+

Public Data

+ + @switch (createdPreprint()?.hasDataLinks) { + @case (ApplicabilityStatus.NotApplicable) { +

Author asserted there is no data associated with this preprint.

+ } + @case (ApplicabilityStatus.Unavailable) { + {{ createdPreprint()?.whyNoData }} + } + @case (ApplicabilityStatus.Applicable) { + @for (link of createdPreprint()?.dataLinks; track $index) { +

{{ link }}

+ } + } + } +
+ +
+

Public Preregistration

+ + @switch (createdPreprint()?.hasPreregLinks) { + @case (ApplicabilityStatus.NotApplicable) { +

+ The author asserts that a preregistration is not applicable because no data collection, extraction, or + analysis is reported in the preprint. +

+ } + @case (ApplicabilityStatus.Unavailable) { + {{ createdPreprint()?.whyNoPrereg }} + } + @case (ApplicabilityStatus.Applicable) { + @switch (createdPreprint()?.preregLinkInfo) { + @case (PreregLinkInfo.Analysis) { +

Analysis

+ } + @case (PreregLinkInfo.Designs) { +

Study Design

+ } + @case (PreregLinkInfo.Both) { +

Both

+ } + } + @for (link of createdPreprint()?.preregLinks; track $index) { +

{{ link }}

+ } + } + } +
+
+
+} + + +
+

Supplements (Optional)

+ @if (preprintProject()) { +

{{ preprintProject()?.name }}

+ } @else { +

Author did not add any supplements for this preprint

+ } +
+
+ +
+ + +
diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.scss b/src/app/features/preprints/components/stepper/review-step/review-step.component.scss new file mode 100644 index 000000000..243cc50eb --- /dev/null +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.scss @@ -0,0 +1,7 @@ +@use "assets/styles/variables" as var; + +.card { + @media (max-width: var.$breakpoint-sm) { + --p-card-body-padding: 0.75rem; + } +} diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts new file mode 100644 index 000000000..b7e9fa071 --- /dev/null +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ReviewStepComponent } from './review-step.component'; + +describe('ReviewStepComponent', () => { + let component: ReviewStepComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ReviewStepComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ReviewStepComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts new file mode 100644 index 000000000..d3d9b2f88 --- /dev/null +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -0,0 +1,62 @@ +import { createDispatchMap, select } from '@ngxs/store'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Tag } from 'primeng/tag'; + +import { DatePipe, TitleCasePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, input, OnInit, signal } from '@angular/core'; + +import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; +import { PreprintProviderDetails } from '@osf/features/preprints/models'; +import { + FetchLicenses, + FetchPreprintProject, + FetchPreprintsSubjects, + SubmitPreprintSelectors, +} from '@osf/features/preprints/store/submit-preprint'; +import { TruncatedTextComponent } from '@shared/components'; +import { ResourceType } from '@shared/enums'; +import { Institution } from '@shared/models'; +import { ContributorsSelectors, GetAllContributors } from '@shared/stores'; + +@Component({ + selector: 'osf-review-step', + imports: [Card, TruncatedTextComponent, Tag, DatePipe, Button, TitleCasePipe], + templateUrl: './review-step.component.html', + styleUrl: './review-step.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReviewStepComponent implements OnInit { + private actions = createDispatchMap({ + getContributors: GetAllContributors, + fetchSubjects: FetchPreprintsSubjects, + fetchLicenses: FetchLicenses, + fetchPreprintProject: FetchPreprintProject, + }); + provider = input.required(); + createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); + + contributors = select(ContributorsSelectors.getContributors); + bibliographicContributors = computed(() => { + return this.contributors().filter((contributor) => contributor.isBibliographic); + }); + subjects = select(SubmitPreprintSelectors.getSelectedSubjects); + affiliatedInstitutions = signal([]); + license = select(SubmitPreprintSelectors.getPreprintLicense); + preprintProject = select(SubmitPreprintSelectors.getPreprintProject); + + readonly ApplicabilityStatus = ApplicabilityStatus; + readonly PreregLinkInfo = PreregLinkInfo; + + ngOnInit(): void { + this.actions.getContributors(this.createdPreprint()!.id, ResourceType.Preprint); + this.actions.fetchSubjects(); + this.actions.fetchLicenses(); + this.actions.fetchPreprintProject(); + } + + submitPreprint() { + //[RNi] TODO: implement submitting + } +} diff --git a/src/app/features/preprints/constants/submit-preprint-steps.const.ts b/src/app/features/preprints/constants/submit-preprint-steps.const.ts index 902e82173..86a1519cb 100644 --- a/src/app/features/preprints/constants/submit-preprint-steps.const.ts +++ b/src/app/features/preprints/constants/submit-preprint-steps.const.ts @@ -3,32 +3,32 @@ import { StepOption } from '@shared/models'; export const submitPreprintSteps: StepOption[] = [ { - index: SubmitSteps.TitleAndAbstract, + index: 0, label: 'Title and Abstract', value: SubmitSteps.TitleAndAbstract, }, { - index: SubmitSteps.File, + index: 1, label: 'File', value: SubmitSteps.File, }, { - index: SubmitSteps.Metadata, + index: 2, label: 'Metadata', value: SubmitSteps.Metadata, }, { - index: SubmitSteps.AuthorAssertions, + index: 3, label: 'Author Assertions', value: SubmitSteps.AuthorAssertions, }, { - index: SubmitSteps.Supplements, + index: 4, label: 'Supplements', value: SubmitSteps.Supplements, }, { - index: SubmitSteps.Review, + index: 5, label: 'Review', value: SubmitSteps.Review, }, diff --git a/src/app/features/preprints/mappers/preprint-providers.mapper.ts b/src/app/features/preprints/mappers/preprint-providers.mapper.ts index 29dd82270..9dfded91a 100644 --- a/src/app/features/preprints/mappers/preprint-providers.mapper.ts +++ b/src/app/features/preprints/mappers/preprint-providers.mapper.ts @@ -18,6 +18,7 @@ export class PreprintProvidersMapper { footerLinksHtml: response.attributes.footer_links, preprintWord: response.attributes.preprint_word, allowSubmissions: response.attributes.allow_submissions, + assertionsEnabled: response.attributes.assertions_enabled, brand: { id: brandRaw.id, name: brandRaw.attributes.name, @@ -29,6 +30,8 @@ export class PreprintProvidersMapper { }, iri: response.links.iri, faviconUrl: response.attributes.assets.favicon, + squareColorNoTransparentImageUrl: response.attributes.assets?.square_color_no_transparent, + reviewsWorkflow: response.attributes.reviews_workflow, }; } diff --git a/src/app/features/preprints/models/preprint-provider-json-api.models.ts b/src/app/features/preprints/models/preprint-provider-json-api.models.ts index f0e7974f8..9b3be7b4a 100644 --- a/src/app/features/preprints/models/preprint-provider-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-provider-json-api.models.ts @@ -18,6 +18,8 @@ export interface PreprintProviderDetailsJsonApi { favicon: string; }; allow_submissions: boolean; + assertions_enabled: boolean; + reviews_workflow: StringOrNull; }; embeds?: { brand: { diff --git a/src/app/features/preprints/models/preprint-provider.models.ts b/src/app/features/preprints/models/preprint-provider.models.ts index 8979fd539..a0d61d178 100644 --- a/src/app/features/preprints/models/preprint-provider.models.ts +++ b/src/app/features/preprints/models/preprint-provider.models.ts @@ -11,10 +11,13 @@ export interface PreprintProviderDetails { footerLinksHtml: string; preprintWord: string; allowSubmissions: boolean; + assertionsEnabled: boolean; + reviewsWorkflow: StringOrNull; brand: Brand; lastFetched?: number; iri: string; faviconUrl: string; + squareColorNoTransparentImageUrl: string; } export interface PreprintProviderShortInfo { diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index 10e735f39..0e602b738 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -31,22 +31,26 @@

{{ 'Add a ' + preprintProvider()!.preprintWor
@switch (currentStep().value) { @case (SubmitStepsEnum.TitleAndAbstract) { - + } @case (SubmitStepsEnum.File) { } @case (SubmitStepsEnum.Metadata) { - + } @case (SubmitStepsEnum.AuthorAssertions) { - + } @case (SubmitStepsEnum.Supplements) { - + + } + @case (SubmitStepsEnum.Review) { + } @default {

No such step

diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index fcb81928b..e19e1bcf3 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -24,6 +24,7 @@ import { SupplementsStepComponent, TitleAndAbstractStepComponent, } from '@osf/features/preprints/components'; +import { ReviewStepComponent } from '@osf/features/preprints/components/stepper/review-step/review-step.component'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; import { SubmitSteps } from '@osf/features/preprints/enums'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; @@ -47,6 +48,7 @@ import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils'; AuthorAssertionsStepComponent, SupplementsStepComponent, AuthorAssertionsStepComponent, + ReviewStepComponent, ], templateUrl: './submit-preprint-stepper.component.html', styleUrl: './submit-preprint-stepper.component.scss', @@ -66,13 +68,26 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { }); readonly SubmitStepsEnum = SubmitSteps; - readonly submitPreprintSteps = submitPreprintSteps; preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); currentStep = signal(submitPreprintSteps[0]); isWeb = toSignal(inject(IS_WEB)); + readonly submitPreprintSteps = submitPreprintSteps + .map((step) => { + if (!this.preprintProvider()?.assertionsEnabled && step.value === SubmitSteps.AuthorAssertions) { + return null; + } + + return step; + }) + .filter((step) => step !== null) + .map((step, index) => ({ + ...step, + index, + })); + constructor() { effect(() => { const provider = this.preprintProvider(); @@ -109,4 +124,14 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { this.currentStep.set(step); } + + moveToNextStep() { + let nextStepIndex = this.currentStep()?.index + 1; + const nextStepValue = this.submitPreprintSteps[nextStepIndex].value; + if (nextStepValue === SubmitSteps.AuthorAssertions && !this.preprintProvider()?.assertionsEnabled) { + nextStepIndex++; + } + + this.currentStep.set(this.submitPreprintSteps[nextStepIndex]); + } } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts index f26023140..5744898d6 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts @@ -63,6 +63,11 @@ export class SubmitPreprintSelectors { return state.licenses.data; } + @Selector([SubmitPreprintState]) + static getPreprintLicense(state: SubmitPreprintStateModel) { + return state.licenses.data.find((l) => l.id === state.createdPreprint.data?.licenseId) || null; + } + @Selector([SubmitPreprintState]) static getSelectedSubjects(state: SubmitPreprintStateModel) { return state.subjects.data; From 19265b602ad01a92ef965e6901ed0ba0de04b397 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 20:57:12 +0300 Subject: [PATCH 34/42] fix(supplement-step): Fixed next button disable status --- .../supplements-step.component.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts index 4d820d84a..a80d93ecc 100644 --- a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.ts @@ -97,20 +97,11 @@ export class SupplementsStepComponent implements OnInit { }); isNextButtonDisabled = computed(() => { - if (this.createdPreprint()?.nodeId) { - return false; + if (this.selectedSupplementOption() === SupplementOptions.CreateNewProject) { + return !this.createProjectFormValid(); } - switch (this.selectedSupplementOption()) { - case SupplementOptions.None: - return true; - case SupplementOptions.ConnectExistingProject: - return !this.createdPreprint()?.nodeId; - case SupplementOptions.CreateNewProject: - return !this.createProjectFormValid(); - default: - return false; - } + return false; }); constructor() { From 4fff0e89f68074cda05c2b8db297db66dabefe41 Mon Sep 17 00:00:00 2001 From: Roma Date: Tue, 8 Jul 2025 20:58:47 +0300 Subject: [PATCH 35/42] fix(submit-stepper): Made steps variable computed based on provider --- .../submit-preprint-stepper.component.html | 6 +-- .../submit-preprint-stepper.component.ts | 39 ++++++++++++------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index b7dbd5a18..7aeb136e9 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -20,7 +20,7 @@

{{ 'Add a ' + preprintProvider()!.preprintWor } @else { @@ -37,7 +37,7 @@

{{ 'Add a ' + preprintProvider()!.preprintWor } @case (SubmitStepsEnum.Metadata) { @@ -53,7 +53,7 @@

{{ 'Add a ' + preprintProvider()!.preprintWor } @case (SubmitStepsEnum.Supplements) { - + } @default {

No such step

diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index e19e1bcf3..778f3ae7b 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -7,6 +7,7 @@ import { map, of } from 'rxjs'; import { ChangeDetectionStrategy, Component, + computed, effect, HostBinding, inject, @@ -71,22 +72,30 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - currentStep = signal(submitPreprintSteps[0]); + currentStep = signal(submitPreprintSteps[4]); isWeb = toSignal(inject(IS_WEB)); - readonly submitPreprintSteps = submitPreprintSteps - .map((step) => { - if (!this.preprintProvider()?.assertionsEnabled && step.value === SubmitSteps.AuthorAssertions) { - return null; - } + readonly submitPreprintSteps = computed(() => { + const provider = this.preprintProvider(); + + if (!provider) { + return []; + } - return step; - }) - .filter((step) => step !== null) - .map((step, index) => ({ - ...step, - index, - })); + return submitPreprintSteps + .map((step) => { + if (!provider.assertionsEnabled && step.value === SubmitSteps.AuthorAssertions) { + return null; + } + + return step; + }) + .filter((step) => step !== null) + .map((step, index) => ({ + ...step, + index, + })); + }); constructor() { effect(() => { @@ -127,11 +136,11 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { moveToNextStep() { let nextStepIndex = this.currentStep()?.index + 1; - const nextStepValue = this.submitPreprintSteps[nextStepIndex].value; + const nextStepValue = this.submitPreprintSteps()[nextStepIndex].value; if (nextStepValue === SubmitSteps.AuthorAssertions && !this.preprintProvider()?.assertionsEnabled) { nextStepIndex++; } - this.currentStep.set(this.submitPreprintSteps[nextStepIndex]); + this.currentStep.set(this.submitPreprintSteps()[nextStepIndex]); } } From 13bdf988216ef9836972c25edd60f7d4900fe45b Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 9 Jul 2025 16:41:37 +0300 Subject: [PATCH 36/42] refactor(subjects): Refactored subjects validation logic --- .../metadata-step.component.html | 2 +- .../metadata-step/metadata-step.component.ts | 4 ++++ .../preprints-subjects.component.html | 1 + .../preprints-subjects.component.ts | 20 ++++++++++++++++++- .../models/submit-preprint-form.models.ts | 2 ++ .../components/metadata/metadata.component.ts | 8 -------- .../registries-subjects.component.ts | 6 +++++- 7 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index cdb5f99ac..97082c583 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -42,7 +42,7 @@

Publication DOI

- +
diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts index d9266fb8b..408025bfd 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts @@ -93,6 +93,10 @@ export class MetadataStepComponent implements OnInit { nonNullable: true, validators: [], }), + subjects: new FormControl([], { + nonNullable: true, + validators: [Validators.required], + }), }); } diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html index 4b926a1c0..0e418b5fc 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html @@ -1,4 +1,5 @@ (); protected actions = createDispatchMap({ fetchSubjects: FetchSubjects, @@ -30,6 +32,12 @@ export class PreprintsSubjectsComponent implements OnInit { updatePreprintsSubjects: UpdatePreprintsSubjects, }); + constructor() { + effect(() => { + this.updateControlState(this.selectedSubjects()); + }); + } + ngOnInit(): void { this.actions.fetchSubjects(this.selectedProviderId()!); this.actions.fetchPreprintsSubjects(); @@ -44,6 +52,16 @@ export class PreprintsSubjectsComponent implements OnInit { } updateSelectedSubjects(subjects: Subject[]) { + this.updateControlState(subjects); this.actions.updatePreprintsSubjects(subjects); } + + updateControlState(value: Subject[]) { + if (this.control()) { + this.control().setValue(value); + this.control().markAsTouched(); + this.control().markAsDirty(); + this.control().updateValueAndValidity(); + } + } } diff --git a/src/app/features/preprints/models/submit-preprint-form.models.ts b/src/app/features/preprints/models/submit-preprint-form.models.ts index d85d06705..3d1ee0a7b 100644 --- a/src/app/features/preprints/models/submit-preprint-form.models.ts +++ b/src/app/features/preprints/models/submit-preprint-form.models.ts @@ -1,6 +1,7 @@ import { FormControl } from '@angular/forms'; import { StringOrNull } from '@core/helpers'; +import { Subject } from '@shared/models'; export interface TitleAndAbstractForm { title: FormControl; @@ -12,4 +13,5 @@ export interface MetadataForm { originalPublicationDate: FormControl; customPublicationCitation: FormControl; tags: FormControl; + subjects: FormControl; } diff --git a/src/app/features/registries/components/metadata/metadata.component.ts b/src/app/features/registries/components/metadata/metadata.component.ts index e17999dc5..1f7b1d5d7 100644 --- a/src/app/features/registries/components/metadata/metadata.component.ts +++ b/src/app/features/registries/components/metadata/metadata.component.ts @@ -55,7 +55,6 @@ export class MetadataComponent implements OnDestroy { private readonly draftId = this.route.snapshot.params['id']; protected readonly draftRegistration = select(RegistriesSelectors.getDraftRegistration); - protected selectedSubjects = select(RegistriesSelectors.getSelectedSubjects); protected actions = createDispatchMap({ deleteDraft: DeleteDraft, @@ -83,13 +82,6 @@ export class MetadataComponent implements OnDestroy { this.initForm(draft); } }); - - effect(() => { - const subjects = this.selectedSubjects(); - if (subjects) { - this.metadataForm.patchValue({ subjects }); - } - }); } private initForm(data: Registration): void { diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts index 6ee971da7..734e4cfc7 100644 --- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts +++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts @@ -1,6 +1,6 @@ import { createDispatchMap, select } from '@ngxs/store'; -import { ChangeDetectionStrategy, Component, inject, input } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core'; import { FormControl } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; @@ -37,6 +37,10 @@ export class RegistriesSubjectsComponent { }); constructor() { + effect(() => { + this.updateControlState(this.selectedSubjects()); + }); + this.actions.fetchSubjects(this.OSF_PROVIDER_ID); this.actions.fetchRegistrationSubjects(this.draftId); } From 62ac09a2f999ba62d03b6b68bce12cf82e3df913 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 9 Jul 2025 16:43:58 +0300 Subject: [PATCH 37/42] feat(review-step): Integrated API to submit preprint --- .../review-step/review-step.component.html | 2 +- .../review-step/review-step.component.ts | 12 +++++-- .../submit-preprint-stepper.component.ts | 2 +- .../preprints/services/preprints.service.ts | 31 +++++++++++++++++++ .../submit-preprint.actions.ts | 10 ++++++ .../submit-preprint/submit-preprint.state.ts | 26 ++++++++++++++++ 6 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html index c85b00f5c..fdee689bc 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.html +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html @@ -205,5 +205,5 @@

Supplements (Optional)

- +
diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts index d3d9b2f88..6ffd3f671 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -5,7 +5,7 @@ import { Card } from 'primeng/card'; import { Tag } from 'primeng/tag'; import { DatePipe, TitleCasePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component, computed, input, OnInit, signal } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject, input, OnInit, signal } from '@angular/core'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { PreprintProviderDetails } from '@osf/features/preprints/models'; @@ -13,11 +13,13 @@ import { FetchLicenses, FetchPreprintProject, FetchPreprintsSubjects, + SubmitPreprint, SubmitPreprintSelectors, } from '@osf/features/preprints/store/submit-preprint'; import { TruncatedTextComponent } from '@shared/components'; import { ResourceType } from '@shared/enums'; import { Institution } from '@shared/models'; +import { ToastService } from '@shared/services'; import { ContributorsSelectors, GetAllContributors } from '@shared/stores'; @Component({ @@ -28,11 +30,13 @@ import { ContributorsSelectors, GetAllContributors } from '@shared/stores'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ReviewStepComponent implements OnInit { + private readonly toastService = inject(ToastService); private actions = createDispatchMap({ getContributors: GetAllContributors, fetchSubjects: FetchPreprintsSubjects, fetchLicenses: FetchLicenses, fetchPreprintProject: FetchPreprintProject, + submitPreprint: SubmitPreprint, }); provider = input.required(); createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); @@ -57,6 +61,10 @@ export class ReviewStepComponent implements OnInit { } submitPreprint() { - //[RNi] TODO: implement submitting + this.actions.submitPreprint().subscribe({ + complete: () => { + this.toastService.showSuccess('Preprint submitted successfully'); + }, + }); } } diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index 778f3ae7b..f0fbab5e1 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -72,7 +72,7 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); - currentStep = signal(submitPreprintSteps[4]); + currentStep = signal(submitPreprintSteps[0]); isWeb = toSignal(inject(IS_WEB)); readonly submitPreprintSteps = computed(() => { diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 0668a8060..09824e4cc 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -46,6 +46,18 @@ export class PreprintsService { ); } + getById(id: string) { + return this.jsonApiService + .get< + JsonApiResponse, null> + >(`${environment.apiUrl}/preprints/${id}/`) + .pipe( + map((response) => { + return PreprintsMapper.fromPreprintJsonApi(response.data); + }) + ); + } + deletePreprint(id: string) { return this.jsonApiService.delete(`${environment.apiUrl}/preprints/${id}/`); } @@ -67,6 +79,25 @@ export class PreprintsService { .pipe(map((response) => PreprintsMapper.fromPreprintJsonApi(response))); } + submitPreprint(preprintId: string) { + return this.jsonApiService.post(`${environment.apiUrl}/preprints/${preprintId}/review_actions/`, { + data: { + type: 'review_actions', + attributes: { + trigger: 'submit', + }, + relationships: { + target: { + data: { + type: 'preprints', + id: preprintId, + }, + }, + }, + }, + }); + } + private mapPreprintDomainToApiPayload(domainPayload: Partial): Partial { const apiPayload: Record = {}; Object.entries(domainPayload).forEach(([key, value]) => { diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts index f379ce1bb..d047a2ea8 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.actions.ts @@ -28,6 +28,12 @@ export class UpdatePreprint { ) {} } +export class FetchPreprintById { + static readonly type = '[Submit Preprint] Get Preprint By Id'; + + constructor(public id: string) {} +} + export class SetSelectedPreprintFileSource { static readonly type = '[Submit Preprint] Set Selected Preprint File Source'; @@ -127,6 +133,10 @@ export class CreateNewProject { ) {} } +export class SubmitPreprint { + static readonly type = '[Submit Preprint] Submit Preprint'; +} + export class ResetStateAndDeletePreprint { static readonly type = '[Submit Preprint] Reset State And Delete Preprint'; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 1cfd51b7c..99bcf89d4 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -27,6 +27,7 @@ import { CreatePreprint, DisconnectProject, FetchLicenses, + FetchPreprintById, FetchPreprintProject, FetchPreprintsSubjects, GetAvailableProjects, @@ -39,6 +40,7 @@ import { SaveLicense, SetSelectedPreprintFileSource, SetSelectedPreprintProviderId, + SubmitPreprint, SubmitPreprintStateModel, UpdatePreprint, UpdatePreprintsSubjects, @@ -135,6 +137,18 @@ export class SubmitPreprintState { ); } + @Action(FetchPreprintById) + getPreprintById(ctx: StateContext, action: FetchPreprintById) { + ctx.setState(patch({ createdPreprint: patch({ isLoading: true }) })); + + return this.preprintsService.getById(action.id).pipe( + tap((preprint) => { + ctx.setState(patch({ createdPreprint: patch({ isLoading: false, data: preprint }) })); + }), + catchError((error) => this.handleError(ctx, 'createdPreprint', error)) + ); + } + @Action(GetPreprintFilesLinks) getPreprintFilesLinks(ctx: StateContext) { const state = ctx.getState(); @@ -520,6 +534,18 @@ export class SubmitPreprintState { ); } + @Action(SubmitPreprint) + submitPreprint(ctx: StateContext) { + const createdPreprintId = ctx.getState().createdPreprint.data!.id; + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) })); + return this.preprintsService.submitPreprint(createdPreprintId).pipe( + tap(() => { + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: false }) })); + }), + catchError((error) => this.handleError(ctx, 'createdPreprint', error)) + ); + } + private handleError( ctx: StateContext, section: keyof SubmitPreprintStateModel, From 1afb51f3667bb80cc776f8e193da72e62e65a309 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 9 Jul 2025 23:59:45 +0300 Subject: [PATCH 38/42] feat(submit-preprint): Handled loosing unsaved data when moving back and away --- .../author-assertions-step.component.html | 2 +- .../author-assertions-step.component.ts | 48 +++++++++----- .../stepper/file-step/file-step.component.ts | 11 +--- .../metadata-step.component.html | 2 +- .../metadata-step/metadata-step.component.ts | 33 +++++++--- .../preprints-subjects.component.ts | 2 +- .../review-step/review-step.component.html | 2 +- .../review-step/review-step.component.ts | 11 +++- .../supplements-step.component.html | 2 +- .../supplements-step.component.ts | 28 +++++++-- .../title-and-abstract-step.component.ts | 63 ++++++++++--------- .../preprints/guards/confirm-leaving.guard.ts | 30 +++++++++ src/app/features/preprints/guards/index.ts | 1 + .../models/can-deactivate.interface.ts | 5 ++ .../submit-preprint-stepper.component.html | 11 ++-- .../submit-preprint-stepper.component.ts | 28 ++++++--- .../features/preprints/preprints.routes.ts | 2 + .../submit-preprint/submit-preprint.model.ts | 1 + .../submit-preprint.selectors.ts | 5 ++ .../submit-preprint/submit-preprint.state.ts | 3 +- 20 files changed, 200 insertions(+), 90 deletions(-) create mode 100644 src/app/features/preprints/guards/confirm-leaving.guard.ts create mode 100644 src/app/features/preprints/guards/index.ts create mode 100644 src/app/features/preprints/models/can-deactivate.interface.ts diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html index dd7e22bb7..86a5a551a 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html @@ -224,7 +224,7 @@

Public Preregistration

- + (); + backClicked = output(); constructor() { effect(() => { @@ -190,25 +193,19 @@ export class AuthorAssertionsStepComponent { }); } - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - $event.preventDefault(); - return false; - } - nextButtonClicked() { - const formValue = this.authorAssertionsForm.value; + const formValue = this.authorAssertionsForm.getRawValue(); const hasCoi = formValue.hasCoi; - const coiStatement = formValue.coiStatement || null; + const coiStatement = formValue.coiStatement; const hasDataLinks = formValue.hasDataLinks; - const whyNoData = formValue.whyNoData || null; - const dataLinks: string[] = formValue.dataLinks || []; + const whyNoData = formValue.whyNoData; + const dataLinks = formValue.dataLinks; const hasPreregLinks = formValue.hasPreregLinks; - const whyNoPrereg = formValue.whyNoPrereg || null; - const preregLinks: string[] = formValue.preregLinks || []; + const whyNoPrereg = formValue.whyNoPrereg; + const preregLinks = formValue.preregLinks; const preregLinkInfo = formValue.preregLinkInfo || undefined; this.actions @@ -225,12 +222,31 @@ export class AuthorAssertionsStepComponent { }) .subscribe({ complete: () => { - this.toastService.showSuccess('Preprint saved successfully.'); + this.toastService.showSuccess('Preprint saved'); this.nextClicked.emit(); }, }); } + backButtonClicked() { + const formValue = this.authorAssertionsForm.getRawValue(); + const changedFields = findChangedFields(formValue, this.createdPreprint()!); + + if (!Object.keys(changedFields).length) { + this.backClicked.emit(); + return; + } + + this.confirmationService.confirmContinue({ + headerKey: 'Discard changes?', + messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + onConfirm: () => { + this.backClicked.emit(); + }, + onReject: () => null, + }); + } + private disableAndClearValidators(control: AbstractControl) { if (control instanceof FormArray) { while (control.length !== 0) { diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts index 327530771..dc4776b2e 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts @@ -15,7 +15,6 @@ import { Component, computed, DestroyRef, - HostListener, inject, input, OnInit, @@ -42,7 +41,7 @@ import { import { FilesTreeActions } from '@osf/features/project/files/models'; import { FilesTreeComponent, IconComponent } from '@shared/components'; import { OsfFile } from '@shared/models'; -import { CustomConfirmationService } from '@shared/services'; +import { CustomConfirmationService, ToastService } from '@shared/services'; @Component({ selector: 'osf-file-step', @@ -64,6 +63,7 @@ import { CustomConfirmationService } from '@shared/services'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class FileStepComponent implements OnInit { + private toastService = inject(ToastService); private customConfirmationService = inject(CustomConfirmationService); private actions = createDispatchMap({ setSelectedFileSource: SetSelectedPreprintFileSource, @@ -148,6 +148,7 @@ export class FileStepComponent implements OnInit { return; } + this.toastService.showSuccess('Preprint saved'); this.nextClicked.emit(); } @@ -164,12 +165,6 @@ export class FileStepComponent implements OnInit { } } - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - $event.preventDefault(); - return false; - } - selectProject(event: SelectChangeEvent) { if (!(event.originalEvent instanceof PointerEvent)) { return; diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index 97082c583..abf857316 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -84,7 +84,7 @@

Publication Citation (optional)

- + (); + backClicked = output(); ngOnInit() { this.actions.fetchLicenses(); @@ -111,17 +115,12 @@ export class MetadataStepComponent implements OnInit { this.actions.updatePreprint(this.createdPreprint()!.id, changedFields).subscribe({ complete: () => { + this.toastService.showSuccess('Preprint saved'); this.nextClicked.emit(); }, }); } - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - $event.preventDefault(); - return false; - } - createLicense(licenseDetails: { id: string; licenseOptions: LicenseOptions }) { this.actions.saveLicense(licenseDetails.id, licenseDetails.licenseOptions); } @@ -135,4 +134,24 @@ export class MetadataStepComponent implements OnInit { tags: updatedTags, }); } + + backButtonClicked() { + const formValue = this.metadataForm.value; + delete formValue.subjects; + const changedFields = findChangedFields(formValue, this.createdPreprint()!); + + if (!Object.keys(changedFields).length) { + this.backClicked.emit(); + return; + } + + this.customConfirmationService.confirmContinue({ + headerKey: 'Discard changes?', + messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + onConfirm: () => { + this.backClicked.emit(); + }, + onReject: () => null, + }); + } } diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts index 87e8413b0..08a59f922 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts @@ -23,7 +23,7 @@ export class PreprintsSubjectsComponent implements OnInit { private readonly selectedProviderId = select(SubmitPreprintSelectors.getSelectedProviderId); protected selectedSubjects = select(SubmitPreprintSelectors.getSelectedSubjects); protected isSubjectsUpdating = select(SubmitPreprintSelectors.isSubjectsUpdating); - protected control = input.required(); + control = input.required(); protected actions = createDispatchMap({ fetchSubjects: FetchSubjects, diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html index fdee689bc..12dc381ee 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.html +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html @@ -204,6 +204,6 @@

Supplements (Optional)

- +
diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts index 6ffd3f671..6ac242963 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -6,6 +6,7 @@ import { Tag } from 'primeng/tag'; import { DatePipe, TitleCasePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, inject, input, OnInit, signal } from '@angular/core'; +import { Router } from '@angular/router'; import { ApplicabilityStatus, PreregLinkInfo } from '@osf/features/preprints/enums'; import { PreprintProviderDetails } from '@osf/features/preprints/models'; @@ -30,7 +31,8 @@ import { ContributorsSelectors, GetAllContributors } from '@shared/stores'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ReviewStepComponent implements OnInit { - private readonly toastService = inject(ToastService); + private router = inject(Router); + private toastService = inject(ToastService); private actions = createDispatchMap({ getContributors: GetAllContributors, fetchSubjects: FetchPreprintsSubjects, @@ -63,8 +65,13 @@ export class ReviewStepComponent implements OnInit { submitPreprint() { this.actions.submitPreprint().subscribe({ complete: () => { - this.toastService.showSuccess('Preprint submitted successfully'); + this.toastService.showSuccess('Preprint submitted'); + this.router.navigateByUrl('/preprints'); }, }); } + + cancelSubmission() { + this.router.navigateByUrl('/preprints'); + } } diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html index b942bf210..4a4b44210 100644 --- a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html @@ -72,7 +72,7 @@

Supplements (optional)

}
- + (); + backClicked = output(); ngOnInit() { this.projectNameControl.valueChanges @@ -204,12 +204,30 @@ export class SupplementsStepComponent implements OnInit { return; } + this.toastService.showSuccess('Preprint saved'); this.nextClicked.emit(); } - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - $event.preventDefault(); - return false; + backButtonClicked() { + const hasData = Object.entries(this.createProjectForm.value).some(([_, value]) => { + if (value instanceof Array) { + return value.length > 0; + } + return !!value; + }); + + if (this.selectedSupplementOption() === SupplementOptions.CreateNewProject && hasData) { + this.customConfirmationService.confirmContinue({ + headerKey: 'Discard changes?', + messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + onConfirm: () => { + this.backClicked.emit(); + }, + onReject: () => null, + }); + return; + } + + this.backClicked.emit(); } } diff --git a/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts index ad43bf852..cd7e645ce 100644 --- a/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts +++ b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.ts @@ -8,7 +8,7 @@ import { Message } from 'primeng/message'; import { Textarea } from 'primeng/textarea'; import { Tooltip } from 'primeng/tooltip'; -import { ChangeDetectionStrategy, Component, HostListener, OnInit, output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, effect, inject, output } from '@angular/core'; import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { RouterLink } from '@angular/router'; @@ -17,6 +17,7 @@ import { TitleAndAbstractForm } from '@osf/features/preprints/models'; import { CreatePreprint, SubmitPreprintSelectors, UpdatePreprint } from '@osf/features/preprints/store/submit-preprint'; import { TextInputComponent } from '@shared/components'; import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; +import { ToastService } from '@shared/services'; import { CustomValidators } from '@shared/utils'; @Component({ @@ -37,40 +38,47 @@ import { CustomValidators } from '@shared/utils'; styleUrl: './title-and-abstract-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class TitleAndAbstractStepComponent implements OnInit { - protected titleAndAbstractForm!: FormGroup; - protected inputLimits = formInputLimits; - protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; +export class TitleAndAbstractStepComponent { + private toastService = inject(ToastService); private actions = createDispatchMap({ createPreprint: CreatePreprint, updatePreprint: UpdatePreprint, }); + protected inputLimits = formInputLimits; + protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; + + protected titleAndAbstractForm = new FormGroup({ + title: new FormControl('', { + nonNullable: true, + validators: [CustomValidators.requiredTrimmed(), Validators.maxLength(this.inputLimits.title.maxLength)], + }), + description: new FormControl('', { + nonNullable: true, + validators: [ + CustomValidators.requiredTrimmed(), + Validators.minLength(this.inputLimits.abstract.minLength), + Validators.maxLength(this.inputLimits.abstract.maxLength), + ], + }), + }); + createdPreprint = select(SubmitPreprintSelectors.getCreatedPreprint); providerId = select(SubmitPreprintSelectors.getSelectedProviderId); isUpdatingPreprint = select(SubmitPreprintSelectors.isPreprintSubmitting); nextClicked = output(); - ngOnInit() { - this.initForm(); - } + constructor() { + effect(() => { + const createdPreprint = this.createdPreprint(); + if (!createdPreprint) return; - initForm() { - this.titleAndAbstractForm = new FormGroup({ - title: new FormControl(this.createdPreprint()?.title || '', { - nonNullable: true, - validators: [CustomValidators.requiredTrimmed(), Validators.maxLength(this.inputLimits.title.maxLength)], - }), - description: new FormControl(this.createdPreprint()?.description || '', { - nonNullable: true, - validators: [ - CustomValidators.requiredTrimmed(), - Validators.minLength(this.inputLimits.abstract.minLength), - Validators.maxLength(this.inputLimits.abstract.maxLength), - ], - }), + this.titleAndAbstractForm.patchValue({ + title: createdPreprint.title, + description: createdPreprint.description, + }); }); } @@ -85,23 +93,16 @@ export class TitleAndAbstractStepComponent implements OnInit { this.actions.updatePreprint(this.createdPreprint()!.id, model).subscribe({ complete: () => { this.nextClicked.emit(); + this.toastService.showSuccess('Preprint saved'); }, }); } else { this.actions.createPreprint(model.title!, model.description!, this.providerId()!).subscribe({ complete: () => { this.nextClicked.emit(); + this.toastService.showSuccess('Preprint saved'); }, }); } } - - @HostListener('window:beforeunload', ['$event']) - public onBeforeUnload($event: BeforeUnloadEvent): boolean { - if (this.createdPreprint() || this.titleAndAbstractForm.value) { - $event.preventDefault(); - return false; - } - return true; - } } diff --git a/src/app/features/preprints/guards/confirm-leaving.guard.ts b/src/app/features/preprints/guards/confirm-leaving.guard.ts new file mode 100644 index 000000000..1d540261a --- /dev/null +++ b/src/app/features/preprints/guards/confirm-leaving.guard.ts @@ -0,0 +1,30 @@ +import { Subject } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanDeactivateFn } from '@angular/router'; + +import { SubmitPreprintStepperComponent } from '@osf/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component'; +import { CustomConfirmationService } from '@shared/services'; + +export const ConfirmLeavingGuard: CanDeactivateFn = (component) => { + const confirmationService = inject(CustomConfirmationService); + const confirmationResultSubject = new Subject(); + const confirmationResultObservable = confirmationResultSubject.asObservable(); + + if (component.canDeactivate()) { + return true; + } + + confirmationService.confirmContinue({ + headerKey: 'You have unsaved changes', + messageKey: 'Are you sure you want to leave?', + onConfirm: () => { + confirmationResultSubject.next(true); + }, + onReject: () => { + confirmationResultSubject.next(false); + }, + }); + + return confirmationResultObservable; +}; diff --git a/src/app/features/preprints/guards/index.ts b/src/app/features/preprints/guards/index.ts new file mode 100644 index 000000000..05326ccfb --- /dev/null +++ b/src/app/features/preprints/guards/index.ts @@ -0,0 +1 @@ +export * from './confirm-leaving.guard'; diff --git a/src/app/features/preprints/models/can-deactivate.interface.ts b/src/app/features/preprints/models/can-deactivate.interface.ts new file mode 100644 index 000000000..ceed90451 --- /dev/null +++ b/src/app/features/preprints/models/can-deactivate.interface.ts @@ -0,0 +1,5 @@ +import { Observable } from 'rxjs'; + +export interface CanDeactivateComponent { + canDeactivate(): Observable | boolean; +} diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html index 7aeb136e9..a3aeefd96 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.html @@ -37,24 +37,21 @@

{{ 'Add a ' + preprintProvider()!.preprintWor } @case (SubmitStepsEnum.Metadata) { - + } @case (SubmitStepsEnum.AuthorAssertions) { - + } @case (SubmitStepsEnum.Supplements) { - + } @case (SubmitStepsEnum.Review) { } - @case (SubmitStepsEnum.Supplements) { - - } @default {

No such step

} diff --git a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts index f0fbab5e1..83bf1ad75 100644 --- a/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts +++ b/src/app/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component.ts @@ -2,7 +2,7 @@ import { createDispatchMap, select } from '@ngxs/store'; import { Skeleton } from 'primeng/skeleton'; -import { map, of } from 'rxjs'; +import { map, Observable, of } from 'rxjs'; import { ChangeDetectionStrategy, @@ -10,6 +10,7 @@ import { computed, effect, HostBinding, + HostListener, inject, OnDestroy, OnInit, @@ -28,10 +29,12 @@ import { import { ReviewStepComponent } from '@osf/features/preprints/components/stepper/review-step/review-step.component'; import { submitPreprintSteps } from '@osf/features/preprints/constants'; import { SubmitSteps } from '@osf/features/preprints/enums'; +import { CanDeactivateComponent } from '@osf/features/preprints/models/can-deactivate.interface'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { ResetStateAndDeletePreprint, SetSelectedPreprintProviderId, + SubmitPreprintSelectors, } from '@osf/features/preprints/store/submit-preprint'; import { StepOption } from '@osf/shared/models'; import { StepperComponent } from '@shared/components'; @@ -55,7 +58,7 @@ import { BrowserTabHelper, HeaderStyleHelper, IS_WEB } from '@shared/utils'; styleUrl: './submit-preprint-stepper.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { +export class SubmitPreprintStepperComponent implements OnInit, OnDestroy, CanDeactivateComponent { @HostBinding('class') classes = 'flex-1 flex flex-column w-full'; private readonly route = inject(ActivatedRoute); @@ -72,6 +75,7 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); + hasBeenSubmitted = select(SubmitPreprintSelectors.hasBeenSubmitted); currentStep = signal(submitPreprintSteps[0]); isWeb = toSignal(inject(IS_WEB)); @@ -114,6 +118,10 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { }); } + canDeactivate(): Observable | boolean { + return this.hasBeenSubmitted(); + } + ngOnInit() { this.actions.getPreprintProviderById(this.providerId()); } @@ -135,12 +143,16 @@ export class SubmitPreprintStepperComponent implements OnInit, OnDestroy { } moveToNextStep() { - let nextStepIndex = this.currentStep()?.index + 1; - const nextStepValue = this.submitPreprintSteps()[nextStepIndex].value; - if (nextStepValue === SubmitSteps.AuthorAssertions && !this.preprintProvider()?.assertionsEnabled) { - nextStepIndex++; - } + this.currentStep.set(this.submitPreprintSteps()[this.currentStep()?.index + 1]); + } + + moveToPreviousStep() { + this.currentStep.set(this.submitPreprintSteps()[this.currentStep()?.index - 1]); + } - this.currentStep.set(this.submitPreprintSteps()[nextStepIndex]); + @HostListener('window:beforeunload', ['$event']) + public onBeforeUnload($event: BeforeUnloadEvent): boolean { + $event.preventDefault(); + return false; } } diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index f2caae2b0..88525e050 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -2,6 +2,7 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; +import { ConfirmLeavingGuard } from '@osf/features/preprints/guards'; import { PreprintsComponent } from '@osf/features/preprints/preprints.component'; import { PreprintSubjectsService } from '@osf/features/preprints/services'; import { PreprintProvidersState } from '@osf/features/preprints/store/preprint-providers'; @@ -71,6 +72,7 @@ export const preprintsRoutes: Routes = [ import('@osf/features/preprints/pages/submit-preprint-stepper/submit-preprint-stepper.component').then( (c) => c.SubmitPreprintStepperComponent ), + canDeactivate: [ConfirmLeavingGuard], }, ], }, diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts index 8d43c442c..2a5115061 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.model.ts @@ -15,4 +15,5 @@ export interface SubmitPreprintStateModel { licenses: AsyncStateModel; subjects: AsyncStateModel; preprintProject: AsyncStateModel; + hasBeenSubmitted: boolean; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts index 5744898d6..08f206c45 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.selectors.ts @@ -87,4 +87,9 @@ export class SubmitPreprintSelectors { static isPreprintProjectLoading(state: SubmitPreprintStateModel) { return state.preprintProject.isLoading; } + + @Selector([SubmitPreprintState]) + static hasBeenSubmitted(state: SubmitPreprintStateModel) { + return state.hasBeenSubmitted; + } } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 99bcf89d4..0f8aa4b41 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -91,6 +91,7 @@ const DefaultState: SubmitPreprintStateModel = { isLoading: false, error: null, }, + hasBeenSubmitted: false, }; @State({ @@ -540,7 +541,7 @@ export class SubmitPreprintState { ctx.setState(patch({ createdPreprint: patch({ isSubmitting: true }) })); return this.preprintsService.submitPreprint(createdPreprintId).pipe( tap(() => { - ctx.setState(patch({ createdPreprint: patch({ isSubmitting: false }) })); + ctx.setState(patch({ createdPreprint: patch({ isSubmitting: false }), hasBeenSubmitted: true })); }), catchError((error) => this.handleError(ctx, 'createdPreprint', error)) ); From 9ce44926859488eb0a297f90e94678b84e77da25 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 10 Jul 2025 00:21:39 +0300 Subject: [PATCH 39/42] fix(conflict): Fixed after merge conflict --- .../preprints-subjects/preprints-subjects.component.html | 5 +++++ .../preprints-subjects/preprints-subjects.component.ts | 7 ++++++- .../stepper/review-step/review-step.component.ts | 9 ++++----- .../registries-subjects/registries-subjects.component.ts | 1 + 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html index 97bd7afd0..52de95f26 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.html @@ -6,4 +6,9 @@ (searchChanged)="searchSubjects($event)" (updateSelection)="updateSelectedSubjects($event)" > + @if (control().errors?.['required'] && (control().touched || control().dirty)) { + + {{ INPUT_VALIDATION_MESSAGES.required | translate }} + + } diff --git a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts index 15bf17ac9..fa5876c58 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/preprints-subjects/preprints-subjects.component.ts @@ -1,6 +1,9 @@ import { createDispatchMap, select } from '@ngxs/store'; +import { TranslatePipe } from '@ngx-translate/core'; + import { Card } from 'primeng/card'; +import { Message } from 'primeng/message'; import { ChangeDetectionStrategy, Component, effect, input, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; @@ -16,10 +19,11 @@ import { SubjectsSelectors, UpdateResourceSubjects, } from '@osf/shared/stores'; +import { INPUT_VALIDATION_MESSAGES } from '@shared/constants'; @Component({ selector: 'osf-preprints-subjects', - imports: [SubjectsComponent, Card], + imports: [SubjectsComponent, Card, Message, TranslatePipe], templateUrl: './preprints-subjects.component.html', styleUrl: './preprints-subjects.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -32,6 +36,7 @@ export class PreprintsSubjectsComponent implements OnInit { protected isSubjectsUpdating = select(SubjectsSelectors.areSelectedSubjectsLoading); control = input.required(); + protected readonly INPUT_VALIDATION_MESSAGES = INPUT_VALIDATION_MESSAGES; protected actions = createDispatchMap({ fetchSubjects: FetchSubjects, fetchSelectedSubjects: FetchSelectedSubjects, diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts index 6ac242963..7b0ec7d77 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -13,7 +13,6 @@ import { PreprintProviderDetails } from '@osf/features/preprints/models'; import { FetchLicenses, FetchPreprintProject, - FetchPreprintsSubjects, SubmitPreprint, SubmitPreprintSelectors, } from '@osf/features/preprints/store/submit-preprint'; @@ -21,7 +20,7 @@ import { TruncatedTextComponent } from '@shared/components'; import { ResourceType } from '@shared/enums'; import { Institution } from '@shared/models'; import { ToastService } from '@shared/services'; -import { ContributorsSelectors, GetAllContributors } from '@shared/stores'; +import { ContributorsSelectors, FetchSelectedSubjects, GetAllContributors, SubjectsSelectors } from '@shared/stores'; @Component({ selector: 'osf-review-step', @@ -35,7 +34,7 @@ export class ReviewStepComponent implements OnInit { private toastService = inject(ToastService); private actions = createDispatchMap({ getContributors: GetAllContributors, - fetchSubjects: FetchPreprintsSubjects, + fetchSubjects: FetchSelectedSubjects, fetchLicenses: FetchLicenses, fetchPreprintProject: FetchPreprintProject, submitPreprint: SubmitPreprint, @@ -47,7 +46,7 @@ export class ReviewStepComponent implements OnInit { bibliographicContributors = computed(() => { return this.contributors().filter((contributor) => contributor.isBibliographic); }); - subjects = select(SubmitPreprintSelectors.getSelectedSubjects); + subjects = select(SubjectsSelectors.getSelectedSubjects); affiliatedInstitutions = signal([]); license = select(SubmitPreprintSelectors.getPreprintLicense); preprintProject = select(SubmitPreprintSelectors.getPreprintProject); @@ -57,7 +56,7 @@ export class ReviewStepComponent implements OnInit { ngOnInit(): void { this.actions.getContributors(this.createdPreprint()!.id, ResourceType.Preprint); - this.actions.fetchSubjects(); + this.actions.fetchSubjects(this.createdPreprint()!.id, ResourceType.Preprint); this.actions.fetchLicenses(); this.actions.fetchPreprintProject(); } diff --git a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts index a49ab36f6..1ccb592aa 100644 --- a/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts +++ b/src/app/features/registries/components/metadata/registries-subjects/registries-subjects.component.ts @@ -2,6 +2,7 @@ import { createDispatchMap, select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; +import { Card } from 'primeng/card'; import { Message } from 'primeng/message'; import { ChangeDetectionStrategy, Component, effect, inject, input } from '@angular/core'; From 7ee579eea323b4c7e1cabd42ccc9d972f0551662 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 10 Jul 2025 00:24:11 +0300 Subject: [PATCH 40/42] fix(submit-preprint): Minor fixes as a result of manual testing --- .../stepper/metadata-step/metadata-step.component.ts | 2 +- .../preprints/models/submit-preprint-form.models.ts | 6 +++--- .../store/submit-preprint/submit-preprint.state.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts index d7ed2a974..af68bdbd3 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.ts @@ -81,7 +81,7 @@ export class MetadataStepComponent implements OnInit { initForm() { const publicationDate = this.createdPreprint()?.originalPublicationDate; this.metadataForm = new FormGroup({ - doi: new FormControl(this.createdPreprint()?.doi || '', { + doi: new FormControl(this.createdPreprint()?.doi || null, { nonNullable: true, validators: [CustomValidators.requiredTrimmed(), Validators.pattern(this.inputLimits.doi.pattern)], }), diff --git a/src/app/features/preprints/models/submit-preprint-form.models.ts b/src/app/features/preprints/models/submit-preprint-form.models.ts index 3d1ee0a7b..eda92dabb 100644 --- a/src/app/features/preprints/models/submit-preprint-form.models.ts +++ b/src/app/features/preprints/models/submit-preprint-form.models.ts @@ -1,7 +1,7 @@ import { FormControl } from '@angular/forms'; import { StringOrNull } from '@core/helpers'; -import { Subject } from '@shared/models'; +import { SubjectModel } from '@shared/models'; export interface TitleAndAbstractForm { title: FormControl; @@ -9,9 +9,9 @@ export interface TitleAndAbstractForm { } export interface MetadataForm { - doi: FormControl; + doi: FormControl; originalPublicationDate: FormControl; customPublicationCitation: FormControl; tags: FormControl; - subjects: FormControl; + subjects: FormControl; } diff --git a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts index 08c8b20e1..7d34f9f45 100644 --- a/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts +++ b/src/app/features/preprints/store/submit-preprint/submit-preprint.state.ts @@ -300,7 +300,7 @@ export class SubmitPreprintState { const state = ctx.getState(); const createdPreprintId = state.createdPreprint.data?.id; ctx.setState({ ...DefaultState }); - if (createdPreprintId) { + if (createdPreprintId && !state.hasBeenSubmitted) { return this.preprintsService.deletePreprint(createdPreprintId); } From f80624510ab08b4f26989df557369537db34dddf Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 10 Jul 2025 17:18:37 +0300 Subject: [PATCH 41/42] feat(submit-preprint): Extracted static string to en.json --- .../author-assertions-step.component.html | 89 +++++---- .../author-assertions-step.component.ts | 10 +- .../file-step/file-step.component.html | 39 ++-- .../stepper/file-step/file-step.component.ts | 10 +- .../metadata-step.component.html | 24 ++- .../metadata-step/metadata-step.component.ts | 6 +- .../review-step/review-step.component.html | 104 +++++++---- .../review-step/review-step.component.ts | 6 +- .../supplements-step.component.html | 28 ++- .../supplements-step.component.ts | 31 ++-- .../title-and-abstract-step.component.html | 24 ++- .../title-and-abstract-step.component.ts | 4 +- .../constants/prereg-link-options.const.ts | 6 +- .../constants/submit-preprint-steps.const.ts | 12 +- .../preprints/guards/confirm-leaving.guard.ts | 4 +- src/assets/i18n/en.json | 170 +++++++++++++++++- 16 files changed, 402 insertions(+), 165 deletions(-) diff --git a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html index 86a5a551a..05675671b 100644 --- a/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html +++ b/src/app/features/preprints/components/stepper/author-assertion-step/author-assertions-step.component.html @@ -1,15 +1,11 @@ -

Author Assertions

+

{{ 'preprints.preprintStepper.authorAssertions.title' | translate }}

-

Conflict of Interest

+

{{ 'preprints.preprintStepper.authorAssertions.conflictOfInterest.title' | translate }}

- The Conflict of Interest (COI) assertion is made on behalf of all the authors listed for this preprint. COIs - include: financial involvement in any entity such as honoraria, grants, speaking fees, employment, consultancies, - stock ownership, expert testimony, and patents or licenses. COIs can also include non-financial interests such as - personal or professional relationships or pre-existing beliefs in the subject matter or materials discussed in - this preprint. + {{ 'preprints.preprintStepper.authorAssertions.conflictOfInterest.description' | translate }}

@@ -20,7 +16,7 @@

Conflict of Interest

[value]="true" [formControl]="authorAssertionsForm.controls['hasCoi']" /> - +
@@ -30,7 +26,7 @@

Conflict of Interest

[value]="false" [formControl]="authorAssertionsForm.controls['hasCoi']" /> - +
@@ -43,8 +39,8 @@

Conflict of Interest

[formControl]="authorAssertionsForm.controls['coiStatement']" [placeholder]=" authorAssertionsForm.value.hasCoi - ? 'Describe' - : 'Author asserted there is no Conflict of Interest with this preprint.' + ? ('preprints.preprintStepper.common.placeholders.describe' | translate) + : ('preprints.preprintStepper.authorAssertions.conflictOfInterest.noCoiPlaceholder' | translate) " > @let coiStatementControl = authorAssertionsForm.controls['coiStatement']; @@ -58,14 +54,10 @@

Conflict of Interest

-

Public Data

+

{{ 'preprints.preprintStepper.authorAssertions.publicData.title' | translate }}

- Data refers to raw and/or processed information (quantitative or qualitative) used for the analyses, case studies, - and/or descriptive interpretation in the preprint. Public data could include data posted to open-access - repositories, public archival library collection, or government archive. For data that is available under limited - circumstances (e.g., after signing a data sharing agreement), choose the ‘No’ option and use the comment box to - explain how others could access the data. + {{ 'preprints.preprintStepper.authorAssertions.publicData.description' | translate }}

@@ -76,7 +68,7 @@

Public Data

[value]="ApplicabilityStatus.Applicable" [formControl]="authorAssertionsForm.controls['hasDataLinks']" /> - +
@@ -86,7 +78,7 @@

Public Data

[value]="ApplicabilityStatus.Unavailable" [formControl]="authorAssertionsForm.controls['hasDataLinks']" /> - +
@@ -96,7 +88,7 @@

Public Data

[value]="ApplicabilityStatus.NotApplicable" [formControl]="authorAssertionsForm.controls['hasDataLinks']" /> - +
@@ -113,8 +105,8 @@

Public Data

[rows]="3" [placeholder]=" hasDataLinks === ApplicabilityStatus.Unavailable - ? 'Describe' - : 'Author asserted there is no data associated with this preprint.' + ? ('preprints.preprintStepper.common.placeholders.describe' | translate) + : ('preprints.preprintStepper.authorAssertions.publicData.notApplicablePlaceholder' | translate) " > @let whyNoDataControl = authorAssertionsForm.controls['whyNoData']; @@ -127,7 +119,7 @@

Public Data

@@ -137,12 +129,10 @@

Public Data

-

Public Preregistration

+

{{ 'preprints.preprintStepper.authorAssertions.publicPreregistration.title' | translate }}

- A preregistration is a description of the research design and/or analysis plan that is created and registered - before researchers collected data or before they have seen/interacted with preexisting data. The description - should appear in a public registry (e.g., clinicaltrials.gov, OSF, AEA registry). + {{ 'preprints.preprintStepper.authorAssertions.publicPreregistration.description' | translate }}

@@ -154,7 +144,7 @@

Public Preregistration

[value]="ApplicabilityStatus.Applicable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@@ -164,7 +154,7 @@

Public Preregistration

[value]="ApplicabilityStatus.Unavailable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@@ -174,7 +164,7 @@

Public Preregistration

[value]="ApplicabilityStatus.NotApplicable" [formControl]="authorAssertionsForm.controls['hasPreregLinks']" /> - +
@let hasPreregLinks = authorAssertionsForm.value.hasPreregLinks!; @@ -190,8 +180,8 @@

Public Preregistration

[formControl]="authorAssertionsForm.controls['whyNoPrereg']" [placeholder]=" hasPreregLinks === ApplicabilityStatus.Unavailable - ? 'Describe' - : 'The author asserts that a preregistration is not applicable because no data collection, extraction, or analysis is reported in the preprint.' + ? ('preprints.preprintStepper.common.placeholders.describe' | translate) + : ('preprints.preprintStepper.authorAssertions.publicPreregistration.notApplicablePlaceholder' | translate) " > @let hasPreregLinksControl = authorAssertionsForm.controls['hasPreregLinks']; @@ -201,22 +191,19 @@

Public Preregistration

} } @else { - +
+ +
@@ -224,12 +211,20 @@

Public Preregistration

- + { - this.toastService.showSuccess('Preprint saved'); + this.toastService.showSuccess('preprints.preprintStepper.common.successMessages.preprintSaved'); this.nextClicked.emit(); }, }); @@ -238,8 +238,8 @@ export class AuthorAssertionsStepComponent { } this.confirmationService.confirmContinue({ - headerKey: 'Discard changes?', - messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + headerKey: 'common.discardChanges.header', + messageKey: 'common.discardChanges.message', onConfirm: () => { this.backClicked.emit(); }, diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.html b/src/app/features/preprints/components/stepper/file-step/file-step.component.html index ddc5a7f97..c3ab6ee12 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.html +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.html @@ -1,8 +1,13 @@ -

File

+

{{ 'preprints.preprintStepper.file.title' | translate }}

-

Please Upload your {{ provider()?.preprintWord | titlecase }}

-

Note: You cannot switch options once a file is attached.

+

+ {{ + 'preprints.preprintStepper.file.uploadDescription' + | translate: { preprintWord: provider()?.preprintWord | titlecase } + }} +

+

{{ 'preprints.preprintStepper.file.note' | translate }}

@@ -12,10 +17,10 @@

File

}" class="file-source-button w-full" [styleClass]="selectedFileSource() ? 'w-full cursor-not-allowed' : 'w-full'" - [label]="'Upload From Your Computer' | titlecase" + [label]="'preprints.preprintStepper.file.uploadFromComputer' | translate | titlecase" severity="secondary" [disabled]="isFileSourceSelected()" - [pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your computer.' : ''" + [pTooltip]="isFileSourceSelected() ? ('preprints.preprintStepper.file.tooltips.computerDisabled' | translate) : ''" tooltipPosition="top" (click)="selectFileSource(PreprintFileSource.Computer)" /> @@ -25,10 +30,10 @@

File

}" class="file-source-button w-full" [styleClass]="isFileSourceSelected() ? 'w-full cursor-not-allowed' : 'w-full'" - [label]="'Select From existing OSF project' | titlecase" + [label]="'preprints.preprintStepper.file.selectFromProject' | translate | titlecase" severity="secondary" [disabled]="isFileSourceSelected() || versionFileMode()" - [pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your project.' : ''" + [pTooltip]="isFileSourceSelected() ? ('preprints.preprintStepper.file.tooltips.projectDisabled' | translate) : ''" tooltipPosition="top" (click)="selectFileSource(PreprintFileSource.Project)" /> @@ -44,7 +49,7 @@

File

raised severity="success" [icon]="'fas fa-upload'" - [label]="'Upload file'" + [label]="'preprints.preprintStepper.file.uploadFileButton' | translate | titlecase" (click)="fileInput.click()" /> @@ -56,8 +61,10 @@

File

@if (selectedFileSource() === PreprintFileSource.Project && !preprintFiles().length && !arePreprintFilesLoading()) {
-

This will make your project public, if it is not already

-

The projects and components for which you have admin access are listed below.

+

{{ 'preprints.preprintStepper.file.projectSelection.description' | translate }}

+

+ {{ 'preprints.preprintStepper.file.projectSelection.subDescription' | translate }} +

File

optionLabel="name" optionValue="id" [formControl]="projectNameControl" - [placeholder]="'Project Title'" + [placeholder]="'preprints.preprintStepper.file.projectSelection.title' | translate" class="w-12 md:w-6" [editable]="true" styleClass="m-t-24" @@ -110,11 +117,17 @@

File

}
- + diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts index dc4776b2e..9d7690adc 100644 --- a/src/app/features/preprints/components/stepper/file-step/file-step.component.ts +++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.ts @@ -1,5 +1,7 @@ import { createDispatchMap, select } from '@ngxs/store'; +import { TranslatePipe } from '@ngx-translate/core'; + import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { DialogService } from 'primeng/dynamicdialog'; @@ -56,6 +58,7 @@ import { CustomConfirmationService, ToastService } from '@shared/services'; Select, ReactiveFormsModule, FilesTreeComponent, + TranslatePipe, ], templateUrl: './file-step.component.html', styleUrl: './file-step.component.scss', @@ -148,7 +151,7 @@ export class FileStepComponent implements OnInit { return; } - this.toastService.showSuccess('Preprint saved'); + this.toastService.showSuccess('preprints.preprintStepper.common.successMessages.preprintSaved'); this.nextClicked.emit(); } @@ -180,9 +183,8 @@ export class FileStepComponent implements OnInit { versionFile() { this.customConfirmationService.confirmContinue({ - headerKey: 'Add a new preprint file', - messageKey: - 'This will allow a new version of the preprint file to be uploaded to the preprint. The existing file will be retained as a version of the preprint.', + headerKey: 'preprints.preprintStepper.file.versionFile.header', + messageKey: 'preprints.preprintStepper.file.versionFile.message', onConfirm: () => { this.versionFileMode.set(true); this.actions.setSelectedFileSource(PreprintFileSource.None); diff --git a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html index 87cc5ca79..7286e901f 100644 --- a/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html +++ b/src/app/features/preprints/components/stepper/metadata-step/metadata-step.component.html @@ -1,4 +1,4 @@ -

Metadata

+

{{ 'preprints.preprintStepper.metadata.title' | translate }}

@@ -24,7 +24,7 @@

{{ 'shared.license.title' | translate }}

-

Publication DOI

+

{{ 'preprints.preprintStepper.metadata.publicationDoi.title' | translate }}

@let doiControl = metadataForm.controls['doi']; @@ -35,7 +35,7 @@

Publication DOI

} @if (doiControl.errors?.['pattern'] && (doiControl.touched || doiControl.dirty)) { Please use a valid DOI format (10.xxxx/xxxxx) + >{{ 'preprints.preprintStepper.metadata.publicationDoi.patternError' | translate }} }
@@ -47,7 +47,7 @@

Publication DOI

-

Tags (optional)

+

{{ 'preprints.preprintStepper.metadata.tagsTitle' | translate }}

@@ -56,7 +56,7 @@

Tags (optional)

-

Publication Date (optional)

+

{{ 'preprints.preprintStepper.metadata.publicationDateTitle' | translate }}

Publication Date (optional)

-

Publication Citation (optional)

+

{{ 'preprints.preprintStepper.metadata.publicationCitationTitle' | translate }}

Publication Citation (optional)

- + { - this.toastService.showSuccess('Preprint saved'); + this.toastService.showSuccess('preprints.preprintStepper.common.successMessages.preprintSaved'); this.nextClicked.emit(); }, }); @@ -146,8 +146,8 @@ export class MetadataStepComponent implements OnInit { } this.customConfirmationService.confirmContinue({ - headerKey: 'Discard changes?', - messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + headerKey: 'common.discardChanges.header', + messageKey: 'common.discardChanges.message', onConfirm: () => { this.backClicked.emit(); }, diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.html b/src/app/features/preprints/components/stepper/review-step/review-step.component.html index 12dc381ee..56fd7c245 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.html +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.html @@ -1,24 +1,32 @@ -

Consent to publish

+

{{ 'preprints.preprintStepper.review.title' | translate | titlecase }}

- By submitting this preprint you confirm that all contributors agree with sharing it and that you have the right to - share this preprint. + {{ 'preprints.preprintStepper.review.consentDescription' | translate }}

- {{ provider()?.name }} uses {{ provider()?.reviewsWorkflow }}. If your preprint is accepted, it will be assigned a DOI - and become publicly accessible via OSF. The preprint file cannot be deleted but it can be updated or modified. + {{ + 'preprints.preprintStepper.review.workflowDescription' + | translate: { providerName: provider()?.name, reviewsWorkflow: provider()?.reviewsWorkflow } + }}

- You can read more about - OSF preprints moderation policies on the OSF support - center. + {{ 'preprints.preprintStepper.review.learnMore' | translate }} + {{ + 'preprints.preprintStepper.review.moderationPolicies' | translate + }} + {{ 'preprints.preprintStepper.review.supportCenter' | translate }}

-

Title and Abstract

+

{{ 'preprints.preprintStepper.review.sections.titleAndAbstract.title' | translate }}

-

{{ provider()?.preprintWord | titlecase }} Service

+

+ {{ + 'preprints.preprintStepper.review.sections.titleAndAbstract.service' + | translate: { preprintWord: provider()?.preprintWord | titlecase } + }} +

Provider logo

{{ provider()?.name }}

@@ -26,12 +34,12 @@

{{ provider()?.preprintWord | titlecase }} Service

-

Title

+

{{ 'preprints.preprintStepper.common.labels.title' | translate }}

{{ createdPreprint()!.title }}

-

Abstract

+

{{ 'preprints.preprintStepper.common.labels.abstract' | translate }}

@@ -39,10 +47,10 @@

Abstract

-

Metadata

+

{{ 'preprints.preprintStepper.review.sections.metadata.title' | translate }}

-

Contributors

+

{{ 'preprints.preprintStepper.review.sections.metadata.contributors' | translate }}

@for (contributor of bibliographicContributors(); track contributor.id) { @@ -56,7 +64,7 @@

Contributors

@if (affiliatedInstitutions().length) {
-

Affiliated Institutions

+

{{ 'preprints.preprintStepper.review.sections.metadata.affiliatedInstitutions' | translate }}

@@ -127,24 +135,24 @@

Publication Citation

@if (provider()?.assertionsEnabled) {
-

Author Assertions

+

{{ 'preprints.preprintStepper.review.sections.authorAssertions.title' | translate }}

-

Conflict of Interest

+

{{ 'preprints.preprintStepper.review.sections.authorAssertions.conflictOfInterest' | translate }}

@if (!createdPreprint()?.hasCoi) { -

Author asserted no Conflict of Interest.

+

{{ 'preprints.preprintStepper.review.sections.authorAssertions.noCoi' | translate }}

} @else { {{ createdPreprint()?.coiStatement }} }
-

Public Data

+

{{ 'preprints.preprintStepper.review.sections.authorAssertions.publicData' | translate }}

@switch (createdPreprint()?.hasDataLinks) { @case (ApplicabilityStatus.NotApplicable) { -

Author asserted there is no data associated with this preprint.

+

{{ 'preprints.preprintStepper.review.sections.authorAssertions.noData' | translate }}

} @case (ApplicabilityStatus.Unavailable) { {{ createdPreprint()?.whyNoData }} @@ -158,13 +166,14 @@

Public Data

-

Public Preregistration

+

+ {{ 'preprints.preprintStepper.review.sections.authorAssertions.publicPreregistration' | translate }} +

@switch (createdPreprint()?.hasPreregLinks) { @case (ApplicabilityStatus.NotApplicable) {

- The author asserts that a preregistration is not applicable because no data collection, extraction, or - analysis is reported in the preprint. + {{ 'preprints.preprintStepper.review.sections.authorAssertions.noPrereg' | translate }}

} @case (ApplicabilityStatus.Unavailable) { @@ -173,13 +182,19 @@

Public Preregistration

@case (ApplicabilityStatus.Applicable) { @switch (createdPreprint()?.preregLinkInfo) { @case (PreregLinkInfo.Analysis) { -

Analysis

+

+ {{ 'preprints.preprintStepper.common.labels.preregTypes.analysis' | translate }} +

} @case (PreregLinkInfo.Designs) { -

Study Design

+

+ {{ 'preprints.preprintStepper.common.labels.preregTypes.designs' | translate }} +

} @case (PreregLinkInfo.Both) { -

Both

+

+ {{ 'preprints.preprintStepper.common.labels.preregTypes.both' | translate }} +

} } @for (link of createdPreprint()?.preregLinks; track $index) { @@ -194,16 +209,27 @@

Public Preregistration

-

Supplements (Optional)

+

{{ 'preprints.preprintStepper.review.sections.supplements.title' | translate }}

@if (preprintProject()) {

{{ preprintProject()?.name }}

} @else { -

Author did not add any supplements for this preprint

+

{{ 'preprints.preprintStepper.review.sections.supplements.noSupplements' | translate }}

}
- - + +
diff --git a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts index 7b0ec7d77..56c4913b6 100644 --- a/src/app/features/preprints/components/stepper/review-step/review-step.component.ts +++ b/src/app/features/preprints/components/stepper/review-step/review-step.component.ts @@ -1,5 +1,7 @@ import { createDispatchMap, select } from '@ngxs/store'; +import { TranslatePipe } from '@ngx-translate/core'; + import { Button } from 'primeng/button'; import { Card } from 'primeng/card'; import { Tag } from 'primeng/tag'; @@ -24,7 +26,7 @@ import { ContributorsSelectors, FetchSelectedSubjects, GetAllContributors, Subje @Component({ selector: 'osf-review-step', - imports: [Card, TruncatedTextComponent, Tag, DatePipe, Button, TitleCasePipe], + imports: [Card, TruncatedTextComponent, Tag, DatePipe, Button, TitleCasePipe, TranslatePipe], templateUrl: './review-step.component.html', styleUrl: './review-step.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, @@ -64,7 +66,7 @@ export class ReviewStepComponent implements OnInit { submitPreprint() { this.actions.submitPreprint().subscribe({ complete: () => { - this.toastService.showSuccess('Preprint submitted'); + this.toastService.showSuccess('preprints.preprintStepper.common.successMessages.preprintSubmitted'); this.router.navigateByUrl('/preprints'); }, }); diff --git a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html index 4a4b44210..dab3ce395 100644 --- a/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html +++ b/src/app/features/preprints/components/stepper/supplements-step/supplements-step.component.html @@ -1,7 +1,7 @@ -

Supplements (optional)

+

{{ 'preprints.preprintStepper.supplements.title' | translate }}

-

Connect an OSF project to share data, code, protocols, or other supplemental materials.

+

{{ 'preprints.preprintStepper.supplements.description' | translate }}

@let createdPreprintNodeId = createdPreprint()?.nodeId; @@ -13,7 +13,7 @@

Supplements (optional)

}" class="supplement-option-button w-full" styleClass="w-full" - [label]="'Connect an existing OSF project' | titlecase" + [label]="'preprints.preprintStepper.supplements.options.connectExisting' | translate | titlecase" severity="secondary" (click)="selectSupplementOption(SupplementOptions.ConnectExistingProject)" /> @@ -23,7 +23,7 @@

Supplements (optional)

}" class="supplement-option-button w-full" styleClass="w-full" - [label]="'Create a new OSF project' | titlecase" + [label]="'preprints.preprintStepper.supplements.options.createNew' | translate | titlecase" severity="secondary" (click)="selectSupplementOption(SupplementOptions.CreateNewProject)" /> @@ -32,15 +32,19 @@

Supplements (optional)

@if (selectedSupplementOption() === SupplementOptions.ConnectExistingProject) {
-

This will make your project public, if it is not already

-

The projects and components for which you have admin access are listed below.

+

+ {{ 'preprints.preprintStepper.supplements.projectSelection.description' | translate }} +

+

+ {{ 'preprints.preprintStepper.supplements.projectSelection.subDescription' | translate }} +

Supplements (optional)

}
- + { - this.toastService.showSuccess('Project connected successfully'); + this.toastService.showSuccess('preprints.preprintStepper.supplements.successMessages.projectConnected'); }, }); } disconnectProject() { this.customConfirmationService.confirmDelete({ - headerKey: 'Disconnect supplemental material', - messageKey: - 'This will disconnect the selected project. You can select new supplemental material or re-add the same supplemental material at a later date.', + headerKey: 'preprints.preprintStepper.supplements.disconnectProject.header', + messageKey: 'preprints.preprintStepper.supplements.disconnectProject.message', onConfirm: () => { this.actions.disconnectProject().subscribe({ complete: () => { this.selectedProjectId.set(null); - this.toastService.showSuccess('Project disconnected successfully'); + this.toastService.showSuccess('preprints.preprintStepper.supplements.successMessages.projectDisconnected'); }, }); }, @@ -192,7 +203,7 @@ export class SupplementsStepComponent implements OnInit { ) .subscribe({ complete: () => { - this.toastService.showSuccess('Project created successfully'); + this.toastService.showSuccess('preprints.preprintStepper.supplements.successMessages.projectCreated'); this.nextClicked.emit(); }, }); @@ -204,7 +215,7 @@ export class SupplementsStepComponent implements OnInit { return; } - this.toastService.showSuccess('Preprint saved'); + this.toastService.showSuccess('preprints.preprintStepper.common.successMessages.preprintSaved'); this.nextClicked.emit(); } @@ -218,8 +229,8 @@ export class SupplementsStepComponent implements OnInit { if (this.selectedSupplementOption() === SupplementOptions.CreateNewProject && hasData) { this.customConfirmationService.confirmContinue({ - headerKey: 'Discard changes?', - messageKey: 'You have unsaved changes in the project creation form. Are you sure you want to go back?', + headerKey: 'preprints.preprintStepper.supplements.discardChanges.header', + messageKey: 'preprints.preprintStepper.supplements.discardChanges.message', onConfirm: () => { this.backClicked.emit(); }, diff --git a/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html index 9a8c1520c..7e835e96c 100644 --- a/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html +++ b/src/app/features/preprints/components/stepper/title-and-abstract-step/title-and-abstract-step.component.html @@ -1,17 +1,17 @@ -

Title and Abstract

+

{{ 'preprints.preprintStepper.titleAndAbstract.title' | translate }}

- +