From 5365bd81f78e4cc02516796bd189423183e6183e Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 16 Jul 2025 15:23:22 +0300 Subject: [PATCH 01/12] fix(todo): Resolved todo depending on preprint details page --- .../create-new-version.component.html | 29 +++++++++---------- .../create-new-version.component.ts | 17 ----------- .../my-preprints/my-preprints.component.ts | 6 ++-- 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/src/app/features/preprints/pages/create-new-version/create-new-version.component.html b/src/app/features/preprints/pages/create-new-version/create-new-version.component.html index 2970e5ae8..d9b4834cd 100644 --- a/src/app/features/preprints/pages/create-new-version/create-new-version.component.html +++ b/src/app/features/preprints/pages/create-new-version/create-new-version.component.html @@ -30,20 +30,17 @@

} - -@if (!this.initMode()) { -
- @switch (currentStep().value) { - @case (PreprintSteps.File) { - - } - @case (PreprintSteps.Review) { - - } +
+ @switch (currentStep().value) { + @case (PreprintSteps.File) { + } -
-} + @case (PreprintSteps.Review) { + + } + } +
diff --git a/src/app/features/preprints/pages/create-new-version/create-new-version.component.ts b/src/app/features/preprints/pages/create-new-version/create-new-version.component.ts index f070a5df6..c88ea7ee8 100644 --- a/src/app/features/preprints/pages/create-new-version/create-new-version.component.ts +++ b/src/app/features/preprints/pages/create-new-version/create-new-version.component.ts @@ -25,7 +25,6 @@ import { createNewVersionStepsConst } from '@osf/features/preprints/constants'; import { PreprintSteps } from '@osf/features/preprints/enums'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; import { - CreateNewVersion, FetchPreprintById, PreprintStepperSelectors, ResetState, @@ -56,7 +55,6 @@ export class CreateNewVersionComponent implements OnInit, OnDestroy, CanDeactiva setSelectedPreprintProviderId: SetSelectedPreprintProviderId, resetState: ResetState, fetchPreprint: FetchPreprintById, - createNewVersion: CreateNewVersion, }); readonly PreprintSteps = PreprintSteps; @@ -68,7 +66,6 @@ export class CreateNewVersionComponent implements OnInit, OnDestroy, CanDeactiva hasBeenSubmitted = select(PreprintStepperSelectors.hasBeenSubmitted); currentStep = signal(createNewVersionStepsConst[0]); isWeb = toSignal(inject(IS_WEB)); - initMode = signal(true); constructor() { effect(() => { @@ -85,20 +82,6 @@ export class CreateNewVersionComponent implements OnInit, OnDestroy, CanDeactiva BrowserTabHelper.updateTabStyles(provider.faviconUrl, provider.name); } }); - - effect(() => { - //[RNi] TODO: move this logic to handler of "Create New Version" button on preprint details page when implemented - const preprint = this.preprint(); - - if (!this.initMode()) return; - if (!preprint) return; - - this.actions.createNewVersion(preprint.id).subscribe({ - complete: () => { - this.initMode.set(false); - }, - }); - }); } ngOnInit() { diff --git a/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts b/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts index ab0c5fa07..adde8b360 100644 --- a/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts +++ b/src/app/features/preprints/pages/my-preprints/my-preprints.component.ts @@ -24,7 +24,7 @@ import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { parseQueryFilterParams } from '@core/helpers'; -import { Preprint, PreprintShortInfo } from '@osf/features/preprints/models'; +import { PreprintShortInfo } from '@osf/features/preprints/models'; import { FetchMyPreprints, PreprintSelectors } from '@osf/features/preprints/store/preprint'; import { ListInfoShortenerComponent, SearchInputComponent, SubHeaderComponent } from '@shared/components'; import { TABLE_PARAMS } from '@shared/constants'; @@ -79,8 +79,8 @@ export class MyPreprintsComponent { this.setupQueryParamsEffect(); } - navigateToPreprintDetails(preprint: Preprint): void { - //[RNi] TODO: Implement redirect when details page is done + navigateToPreprintDetails(preprint: PreprintShortInfo): void { + this.router.navigateByUrl(`/preprints/${preprint.providerId}/${preprint.id}`); } onPageChange(event: TablePageEvent): void { From 386e45a58d908aa08b0b7866cc6f116c2572f7b3 Mon Sep 17 00:00:00 2001 From: Roma Date: Wed, 16 Jul 2025 15:24:00 +0300 Subject: [PATCH 02/12] feat(preprint-short-info): Added providerId field to model --- src/app/features/preprints/mappers/preprints.mapper.ts | 6 +++++- .../features/preprints/models/preprint-json-api.models.ts | 6 ++++++ src/app/features/preprints/models/preprint.models.ts | 1 + src/app/features/preprints/services/preprints.service.ts | 7 +++++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 34c7f4951..1218d3514 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -86,7 +86,10 @@ export class PreprintsMapper { } static fromMyPreprintJsonApi( - response: JsonApiResponseWithPaging[], null> + response: JsonApiResponseWithPaging< + ApiData[], + null + > ): PreprintShortInfoWithTotalCount { return { data: response.data.map((preprintData) => { @@ -100,6 +103,7 @@ export class PreprintsMapper { name: contrData.embeds.users.data.attributes.full_name, }; }), + providerId: preprintData.relationships.provider.data.id, }; }), totalCount: response.links.meta.total, 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 36fa5ab84..e07a059a1 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -53,6 +53,12 @@ export interface PreprintRelationshipsJsonApi { type: 'nodes'; }; }; + provider: { + data: { + id: string; + type: 'preprint-providers'; + }; + }; } export interface PreprintEmbedsJsonApi { diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index ee93fc534..90cdabde7 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -41,6 +41,7 @@ export interface PreprintShortInfo { title: string; dateModified: string; contributors: IdName[]; + providerId: string; } export interface PreprintShortInfoWithTotalCount { diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 9d9a5ffd7..4bc3f9450 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -114,14 +114,17 @@ export class PreprintsService { const params: Record = { 'embed[]': ['bibliographic_contributors'], 'fields[users]': 'family_name,full_name,given_name,middle_name', - 'fields[preprints]': 'title,date_modified,public,bibliographic_contributors', + 'fields[preprints]': 'title,date_modified,public,bibliographic_contributors,provider', }; searchPreferencesToJsonApiQueryParams(params, pageNumber, pageSize, filters, preprintSortFieldMap); return this.jsonApiService .get< - JsonApiResponseWithPaging[], null> + JsonApiResponseWithPaging< + ApiData[], + null + > >(`${environment.apiUrl}/users/me/preprints/`, params) .pipe(map((response) => PreprintsMapper.fromMyPreprintJsonApi(response))); } From 2bdb43e8bc9e8395c65f9b8ec1d17ed37782a2c6 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 11:50:09 +0300 Subject: [PATCH 03/12] feat(preprint-file-section): Implemented file section for preprint details page --- .../preprint-file-section.component.html | 44 +++++++++++++++ .../preprint-file-section.component.scss | 11 ++++ .../preprint-file-section.component.spec.ts | 22 ++++++++ .../preprint-file-section.component.ts | 53 +++++++++++++++++++ .../services/preprint-files.service.ts | 4 +- .../store/preprint/preprint.actions.ts | 8 +++ .../store/preprint/preprint.model.ts | 15 +++++- .../store/preprint/preprint.selectors.ts | 25 +++++++++ .../store/preprint/preprint.state.ts | 38 ++++++++++++- src/app/shared/mappers/files/files.mapper.ts | 21 +++++++- .../files/file-version-json-api.model.ts | 17 ++++++ .../shared/models/files/file-version.model.ts | 7 +++ .../models/files/get-files-response.model.ts | 8 ++- src/app/shared/models/index.ts | 2 + src/app/shared/services/files.service.ts | 31 +++++++++-- 15 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html create mode 100644 src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss create mode 100644 src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.spec.ts create mode 100644 src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts create mode 100644 src/app/shared/models/files/file-version-json-api.model.ts create mode 100644 src/app/shared/models/files/file-version.model.ts diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html new file mode 100644 index 000000000..15c2ed1e5 --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html @@ -0,0 +1,44 @@ +
+ @if (safeLink()) { + + } + @if (isIframeLoading || isFileLoading()) { + + } + +
+ @if (!areFileVersionsLoading()) { + @let fileVersionsValue = fileVersions(); + +
+

{{ fileVersionsValue[0].name }}

+

Version: {{ fileVersionsValue[0].id }}

+ + + +
+ } @else { + + } + + @if (!isFileLoading()) { + @let fileValue = file()!; + +
+ Submitted: {{ fileValue.dateCreated | date: 'longDate' }} + | + Last edited: {{ fileValue.dateModified | date: 'longDate' }} +
+ } @else { + + } +
+
diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss new file mode 100644 index 000000000..1f108f29c --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss @@ -0,0 +1,11 @@ +.file-section-height { + min-height: 400px; + + &.medium { + min-height: 700px; + } + + &.large { + min-height: 1100px; + } +} diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.spec.ts b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.spec.ts new file mode 100644 index 000000000..627b4a4d8 --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PreprintFileSectionComponent } from './preprint-file-section.component'; + +describe('PreprintFileSectionComponent', () => { + let component: PreprintFileSectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PreprintFileSectionComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(PreprintFileSectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts new file mode 100644 index 000000000..fa73e4d6d --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts @@ -0,0 +1,53 @@ +import { select } from '@ngxs/store'; + +import { Button } from 'primeng/button'; +import { Menu } from 'primeng/menu'; +import { Skeleton } from 'primeng/skeleton'; + +import { DatePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { DomSanitizer } from '@angular/platform-browser'; + +import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; +import { LoadingSpinnerComponent } from '@shared/components'; +import { IS_LARGE, IS_MEDIUM } from '@shared/utils'; + +@Component({ + selector: 'osf-preprint-file-section', + imports: [LoadingSpinnerComponent, DatePipe, Skeleton, Menu, Button], + templateUrl: './preprint-file-section.component.html', + styleUrl: './preprint-file-section.component.scss', + providers: [DatePipe], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PreprintFileSectionComponent { + private readonly sanitizer = inject(DomSanitizer); + private readonly datePipe = inject(DatePipe); + + isMedium = toSignal(inject(IS_MEDIUM)); + isLarge = toSignal(inject(IS_LARGE)); + + file = select(PreprintSelectors.getPreprintFile); + isFileLoading = select(PreprintSelectors.isPreprintFileLoading); + safeLink = computed(() => { + const link = this.file()?.links.render; + if (!link) return; + + return this.sanitizer.bypassSecurityTrustResourceUrl(link); + }); + isIframeLoading = true; + + fileVersions = select(PreprintSelectors.getPreprintFileVersions); + areFileVersionsLoading = select(PreprintSelectors.arePreprintFileVersionsLoading); + + versionMenuItems = computed(() => { + const fileVersions = this.fileVersions(); + if (!fileVersions.length) return []; + + return fileVersions.map((version, index) => ({ + label: `Version ${++index}, ${this.datePipe.transform(version.dateCreated, 'mm/dd/yyyy hh:mm:ss')}`, + url: version.downloadLink, + })); + }); +} diff --git a/src/app/features/preprints/services/preprint-files.service.ts b/src/app/features/preprints/services/preprint-files.service.ts index a019b2756..ac0c3f707 100644 --- a/src/app/features/preprints/services/preprint-files.service.ts +++ b/src/app/features/preprints/services/preprint-files.service.ts @@ -3,7 +3,7 @@ import { map, Observable, switchMap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; 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, @@ -64,7 +64,7 @@ export class PreprintFilesService { 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) + .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/store/preprint/preprint.actions.ts b/src/app/features/preprints/store/preprint/preprint.actions.ts index 3fbaf5c68..9fe9a7763 100644 --- a/src/app/features/preprints/store/preprint/preprint.actions.ts +++ b/src/app/features/preprints/store/preprint/preprint.actions.ts @@ -15,3 +15,11 @@ export class FetchPreprintById { constructor(public id: string) {} } + +export class FetchPreprintFile { + static readonly type = '[Preprint] Fetch Preprint File'; +} + +export class FetchPreprintFileVersions { + static readonly type = '[Preprint] Fetch Preprint File Versions'; +} diff --git a/src/app/features/preprints/store/preprint/preprint.model.ts b/src/app/features/preprints/store/preprint/preprint.model.ts index e9dd437eb..1334a76b2 100644 --- a/src/app/features/preprints/store/preprint/preprint.model.ts +++ b/src/app/features/preprints/store/preprint/preprint.model.ts @@ -1,9 +1,11 @@ import { Preprint, PreprintShortInfo } from '@osf/features/preprints/models'; -import { AsyncStateModel, AsyncStateWithTotalCount } from '@shared/models'; +import { AsyncStateModel, AsyncStateWithTotalCount, OsfFile, OsfFileVersion } from '@shared/models'; export interface PreprintStateModel { myPreprints: AsyncStateWithTotalCount; preprint: AsyncStateModel; + preprintFile: AsyncStateModel; + fileVersions: AsyncStateModel; } export const DefaultState: PreprintStateModel = { @@ -19,4 +21,15 @@ export const DefaultState: PreprintStateModel = { error: null, totalCount: 0, }, + preprintFile: { + data: null, + isLoading: false, + error: null, + isSubmitting: false, + }, + fileVersions: { + data: [], + isLoading: false, + error: null, + }, }; diff --git a/src/app/features/preprints/store/preprint/preprint.selectors.ts b/src/app/features/preprints/store/preprint/preprint.selectors.ts index 206c8d78c..f4ceacf9f 100644 --- a/src/app/features/preprints/store/preprint/preprint.selectors.ts +++ b/src/app/features/preprints/store/preprint/preprint.selectors.ts @@ -28,4 +28,29 @@ export class PreprintSelectors { static isPreprintSubmitting(state: PreprintStateModel) { return state.preprint.isSubmitting; } + + @Selector([PreprintState]) + static isPreprintLoading(state: PreprintStateModel) { + return state.preprint.isLoading; + } + + @Selector([PreprintState]) + static getPreprintFile(state: PreprintStateModel) { + return state.preprintFile.data; + } + + @Selector([PreprintState]) + static isPreprintFileLoading(state: PreprintStateModel) { + return state.preprintFile.isLoading; + } + + @Selector([PreprintState]) + static getPreprintFileVersions(state: PreprintStateModel) { + return state.fileVersions.data; + } + + @Selector([PreprintState]) + static arePreprintFileVersionsLoading(state: PreprintStateModel) { + return state.fileVersions.isLoading; + } } diff --git a/src/app/features/preprints/store/preprint/preprint.state.ts b/src/app/features/preprints/store/preprint/preprint.state.ts index 9f104b5a2..e0253317a 100644 --- a/src/app/features/preprints/store/preprint/preprint.state.ts +++ b/src/app/features/preprints/store/preprint/preprint.state.ts @@ -1,4 +1,4 @@ -import { Action, State, StateContext } from '@ngxs/store'; +import { Action, State, StateContext, Store } from '@ngxs/store'; import { patch } from '@ngxs/store/operators'; import { tap } from 'rxjs'; @@ -8,8 +8,9 @@ import { inject, Injectable } from '@angular/core'; import { handleSectionError } from '@core/handlers'; import { PreprintsService } from '@osf/features/preprints/services'; +import { FilesService } from '@shared/services'; -import { FetchMyPreprints, FetchPreprintById } from './preprint.actions'; +import { FetchMyPreprints, FetchPreprintById, FetchPreprintFile, FetchPreprintFileVersions } from './preprint.actions'; import { DefaultState, PreprintStateModel } from './preprint.model'; @State({ @@ -18,7 +19,9 @@ import { DefaultState, PreprintStateModel } from './preprint.model'; }) @Injectable() export class PreprintState { + private store = inject(Store); private preprintsService = inject(PreprintsService); + private fileService = inject(FilesService); @Action(FetchMyPreprints) fetchMyPreprints(ctx: StateContext, action: FetchMyPreprints) { @@ -47,8 +50,39 @@ export class PreprintState { return this.preprintsService.getById(action.id).pipe( tap((preprint) => { ctx.setState(patch({ preprint: patch({ isLoading: false, data: preprint }) })); + this.store.dispatch(new FetchPreprintFile()); }), catchError((error) => handleSectionError(ctx, 'preprint', error)) ); } + + @Action(FetchPreprintFile) + fetchPreprintFile(ctx: StateContext) { + const preprintFileId = ctx.getState().preprint.data?.primaryFileId; + if (!preprintFileId) return; + ctx.setState(patch({ preprintFile: patch({ isLoading: true }) })); + + return this.fileService.getFileById(preprintFileId!).pipe( + tap((file) => { + ctx.setState(patch({ preprintFile: patch({ isLoading: false, data: file }) })); + this.store.dispatch(new FetchPreprintFileVersions()); + }), + catchError((error) => handleSectionError(ctx, 'preprintFile', error)) + ); + } + + @Action(FetchPreprintFileVersions) + fetchPreprintFileVersions(ctx: StateContext) { + const fileId = ctx.getState().preprintFile.data?.id; + if (!fileId) return; + + ctx.setState(patch({ fileVersions: patch({ isLoading: true }) })); + + return this.fileService.getFileVersions(fileId).pipe( + tap((fileVersions) => { + ctx.setState(patch({ fileVersions: patch({ isLoading: false, data: fileVersions }) })); + }), + catchError((error) => handleSectionError(ctx, 'fileVersions', error)) + ); + } } diff --git a/src/app/shared/mappers/files/files.mapper.ts b/src/app/shared/mappers/files/files.mapper.ts index 7a4bde085..7ed7e83d8 100644 --- a/src/app/shared/mappers/files/files.mapper.ts +++ b/src/app/shared/mappers/files/files.mapper.ts @@ -1,6 +1,13 @@ import { ApiData } from '@core/models'; import { FileTargetResponse } from '@osf/features/project/files/models/responses/get-file-target-response.model'; -import { FileLinks, FileRelationshipsResponse, FileResponse, OsfFile } from '@osf/shared/models'; +import { + FileLinks, + FileRelationshipsResponse, + FileResponse, + FileVersionsResponseJsonApi, + OsfFile, + OsfFileVersion, +} from '@osf/shared/models'; export function MapFiles( files: ApiData[] @@ -57,3 +64,15 @@ export function MapFile( }, } as OsfFile; } + +export function MapFileVersions(fileVersions: FileVersionsResponseJsonApi): OsfFileVersion[] { + return fileVersions.data.map((fileVersion) => { + return { + id: fileVersion.id, + size: fileVersion.attributes.size, + dateCreated: fileVersion.attributes.date_created, + name: fileVersion.attributes.name, + downloadLink: fileVersion.links.download, + }; + }); +} diff --git a/src/app/shared/models/files/file-version-json-api.model.ts b/src/app/shared/models/files/file-version-json-api.model.ts new file mode 100644 index 000000000..15122aeef --- /dev/null +++ b/src/app/shared/models/files/file-version-json-api.model.ts @@ -0,0 +1,17 @@ +import { ApiData, JsonApiResponse } from '@core/models'; + +export type FileVersionsResponseJsonApi = JsonApiResponse< + ApiData[], + null +>; + +export interface FileVersionAttributesJsonApi { + size: number; + content_type: string; + date_created: Date; + name: string; +} + +export interface FileVersionLinksJsonApi { + download: string; +} diff --git a/src/app/shared/models/files/file-version.model.ts b/src/app/shared/models/files/file-version.model.ts new file mode 100644 index 000000000..dfcf5855d --- /dev/null +++ b/src/app/shared/models/files/file-version.model.ts @@ -0,0 +1,7 @@ +export interface OsfFileVersion { + id: string; + size: number; + dateCreated: Date; + name: string; + downloadLink: string; +} diff --git a/src/app/shared/models/files/get-files-response.model.ts b/src/app/shared/models/files/get-files-response.model.ts index 22b774ced..bc3457110 100644 --- a/src/app/shared/models/files/get-files-response.model.ts +++ b/src/app/shared/models/files/get-files-response.model.ts @@ -1,11 +1,9 @@ import { ApiData, JsonApiResponse } from '@core/models'; import { FileTargetResponse } from '@osf/features/project/files/models/responses/get-file-target-response.model'; -export type GetFilesResponse = JsonApiResponse< - ApiData[], - null ->; -export type GetFileResponse = ApiData; +export type GetFilesResponse = JsonApiResponse; +export type GetFileResponse = JsonApiResponse; +export type FileData = ApiData; export type AddFileResponse = ApiData; export interface FileResponse { diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 3b9d4951c..7f7329fa3 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -8,6 +8,8 @@ export * from './contributors'; export * from './create-component-form.model'; export * from './file-menu-action.model'; export * from './files/file.model'; +export * from './files/file-version.model'; +export * from './files/file-version-json-api.model'; export * from './files/get-configured-storage-addons.model'; export * from './files/get-files-response.model'; export * from './filter-labels.model'; diff --git a/src/app/shared/services/files.service.ts b/src/app/shared/services/files.service.ts index fd33304ab..2dfe49cfa 100644 --- a/src/app/shared/services/files.service.ts +++ b/src/app/shared/services/files.service.ts @@ -6,7 +6,13 @@ import { inject, Injectable } from '@angular/core'; import { ApiData, JsonApiResponse } from '@core/models'; import { JsonApiService } from '@core/services'; -import { MapFile, MapFileCustomMetadata, MapFileRevision, MapFiles } from '@osf/features/project/files/mappers'; +import { + MapFile, + MapFileCustomMetadata, + MapFileRevision, + MapFiles, + MapFileVersions, +} from '@osf/features/project/files/mappers'; import { CreateFolderResponse, FileCustomMetadata, @@ -30,8 +36,10 @@ import { GetFileResponse, GetFilesResponse, OsfFile, + OsfFileVersion, } from '@shared/models'; import { ConfiguredStorageAddon } from '@shared/models/addons/configured-storage-addon.model'; +import { FileVersionsResponseJsonApi } from '@shared/models/files/file-version-json-api.model'; import { GetConfiguredStorageAddonsJsonApi } from '@shared/models/files/get-configured-storage-addons.model'; import { ToastService } from '@shared/services/toast.service'; @@ -94,7 +102,7 @@ export class FilesService { } getFolder(link: string): Observable { - return this.#jsonApiService.get>(link).pipe( + return this.#jsonApiService.get(link).pipe( map((response) => MapFile(response.data)), catchError((error) => { this.toastService.showError(error.error.message); @@ -124,7 +132,7 @@ export class FilesService { resource: resourceId, }; - return this.#jsonApiService.post>(link, body).pipe( + return this.#jsonApiService.post(link, body).pipe( map((response) => MapFile(response.data)), catchError((error) => { this.toastService.showError(error.error.message); @@ -146,6 +154,23 @@ export class FilesService { .pipe(map((response) => MapFile(response.data))); } + getFileById(fileGuid: string): Observable { + return this.#jsonApiService + .get(`${environment.apiUrl}/files/${fileGuid}/`) + .pipe(map((response) => MapFile(response.data))); + } + + getFileVersions(fileGuid: string): Observable { + const params = { + sort: '-id', + 'page[size]': 50, + }; + + return this.#jsonApiService + .get(`${environment.apiUrl}/files/${fileGuid}/versions/`, params) + .pipe(map((response) => MapFileVersions(response))); + } + getFileMetadata(fileGuid: string): Observable { return this.#jsonApiService .get(`${environment.apiUrl}/custom_file_metadata_records/${fileGuid}/`) From 895ee7667bd22e5064548773f100d05be59fde53 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 11:52:20 +0300 Subject: [PATCH 04/12] feat(preprint-details): Implemented basic logic&layout for details page --- .../preprint-details.component.html | 47 +++++++++++++ .../preprint-details.component.scss | 0 .../preprint-details.component.spec.ts | 22 ++++++ .../preprint-details.component.ts | 68 +++++++++++++++++++ .../features/preprints/preprints.routes.ts | 7 ++ 5 files changed, 144 insertions(+) create mode 100644 src/app/features/preprints/pages/preprint-details/preprint-details.component.html create mode 100644 src/app/features/preprints/pages/preprint-details/preprint-details.component.scss create mode 100644 src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts create mode 100644 src/app/features/preprints/pages/preprint-details/preprint-details.component.ts diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html new file mode 100644 index 000000000..961f90e8b --- /dev/null +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html @@ -0,0 +1,47 @@ +
+
+ @if (isPreprintProviderLoading() || isPreprintLoading()) { + + + } @else { + Provider Logo +

{{ preprint()!.title }}

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

Status banner here

+
+ +
+
+ +
+ +
+ +

section with download

+
+ +

section with abstract

+
+ +

section with license

+
+
+
+
diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.scss b/src/app/features/preprints/pages/preprint-details/preprint-details.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts new file mode 100644 index 000000000..68e01a426 --- /dev/null +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PreprintDetailsComponent } from './preprint-details.component'; + +describe('PreprintDetailsComponent', () => { + let component: PreprintDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PreprintDetailsComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(PreprintDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts new file mode 100644 index 000000000..ddd746459 --- /dev/null +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -0,0 +1,68 @@ +import { createDispatchMap, select, Store } from '@ngxs/store'; + +import { Button } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Skeleton } from 'primeng/skeleton'; + +import { map, of } from 'rxjs'; + +import { ChangeDetectionStrategy, Component, HostBinding, inject, OnDestroy, OnInit } from '@angular/core'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { PreprintFileSectionComponent } from '@osf/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component'; +import { FetchPreprintById, PreprintSelectors } from '@osf/features/preprints/store/preprint'; +import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; +import { CreateNewVersion, PreprintStepperSelectors, ResetState } from '@osf/features/preprints/store/preprint-stepper'; + +@Component({ + selector: 'osf-preprint-details', + imports: [Skeleton, PreprintFileSectionComponent, Card, Button], + templateUrl: './preprint-details.component.html', + styleUrl: './preprint-details.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PreprintDetailsComponent implements OnInit, OnDestroy { + @HostBinding('class') classes = 'flex-1 flex flex-column w-full'; + + private readonly route = inject(ActivatedRoute); + private readonly store = inject(Store); + private readonly router = inject(Router); + + private providerId = toSignal(this.route.params.pipe(map((params) => params['providerId'])) ?? of(undefined)); + private preprintId = toSignal(this.route.params.pipe(map((params) => params['preprintId'])) ?? of(undefined)); + + private actions = createDispatchMap({ + getPreprintProviderById: GetPreprintProviderById, + resetState: ResetState, + fetchPreprintById: FetchPreprintById, + createNewVersion: CreateNewVersion, + }); + + preprintProvider = select(PreprintProvidersSelectors.getPreprintProviderDetails(this.providerId())); + isPreprintProviderLoading = select(PreprintProvidersSelectors.isPreprintProviderDetailsLoading); + preprint = select(PreprintSelectors.getPreprint); + isPreprintLoading = select(PreprintSelectors.isPreprintLoading); + + ngOnInit() { + this.actions.fetchPreprintById(this.preprintId()); + this.actions.getPreprintProviderById(this.providerId()); + } + + ngOnDestroy() { + this.actions.resetState(); + } + + editPreprintClicked() { + this.router.navigate(['preprints', this.providerId(), 'edit', this.preprintId()]); + } + + createNewVersionClicked() { + this.actions.createNewVersion(this.preprintId()).subscribe({ + complete: () => { + const newVersionPreprint = this.store.selectSnapshot(PreprintStepperSelectors.getPreprint); + this.router.navigate(['preprints', this.providerId(), 'new-version', newVersionPreprint!.id]); + }, + }); + } +} diff --git a/src/app/features/preprints/preprints.routes.ts b/src/app/features/preprints/preprints.routes.ts index 1dde64cd4..3580625c6 100644 --- a/src/app/features/preprints/preprints.routes.ts +++ b/src/app/features/preprints/preprints.routes.ts @@ -108,6 +108,13 @@ export const preprintsRoutes: Routes = [ ), canDeactivate: [ConfirmLeavingGuard], }, + { + path: ':providerId/:preprintId', + loadComponent: () => + import('@osf/features/preprints/pages/preprint-details/preprint-details.component').then( + (c) => c.PreprintDetailsComponent + ), + }, ], }, ]; From ce9bf28c1885d8b40909c7fe33ab5f45b09d5126 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 12:03:39 +0300 Subject: [PATCH 05/12] style(preprint-details): Removed file frame border, adjusted layout for tablet --- .../preprint-file-section.component.html | 20 ++++++++++++------- .../preprint-file-section.component.scss | 4 ++++ .../preprint-file-section.component.ts | 2 +- .../preprint-details.component.html | 6 +++--- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html index 15c2ed1e5..a6fc9a467 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html @@ -7,7 +7,7 @@ title="Rendering of document" allowfullscreen="" width="100%" - class="flex-1" + class="flex-1 border-none" > } @if (isIframeLoading || isFileLoading()) { @@ -15,7 +15,7 @@ }
- @if (!areFileVersionsLoading()) { + @if (fileVersions()?.length) { @let fileVersionsValue = fileVersions();
@@ -25,19 +25,25 @@
- } @else { + } + + @if (areFileVersionsLoading()) { } - @if (!isFileLoading()) { + @if (file()) { @let fileValue = file()!; -
+
Submitted: {{ fileValue.dateCreated | date: 'longDate' }} - | + @if (isMedium() || isLarge()) { + | + } Last edited: {{ fileValue.dateModified | date: 'longDate' }}
- } @else { + } + + @if (isFileLoading()) { }
diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss index 1f108f29c..dd2760387 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.scss @@ -1,4 +1,8 @@ .file-section-height { + border: 1px solid var(--grey-2); + border-radius: 0.5rem; + padding: 1.25rem; + min-height: 400px; &.medium { diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts index fa73e4d6d..0dd01b7e4 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.ts @@ -32,7 +32,7 @@ export class PreprintFileSectionComponent { isFileLoading = select(PreprintSelectors.isPreprintFileLoading); safeLink = computed(() => { const link = this.file()?.links.render; - if (!link) return; + if (!link) return null; return this.sanitizer.bypassSecurityTrustResourceUrl(link); }); diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html index 961f90e8b..bbcf9a070 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html @@ -27,12 +27,12 @@

{{ preprint()!.title }}

Status banner here

-
-
+
+
-
+

section with download

From 9f64be8241332060dff811f1a0c47ed7ff7bc737 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 13:12:01 +0300 Subject: [PATCH 06/12] feat(preprint-state): Added ResetState action --- .../preprints/store/preprint/preprint.actions.ts | 4 ++++ .../preprints/store/preprint/preprint.state.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/app/features/preprints/store/preprint/preprint.actions.ts b/src/app/features/preprints/store/preprint/preprint.actions.ts index 9fe9a7763..e287a330d 100644 --- a/src/app/features/preprints/store/preprint/preprint.actions.ts +++ b/src/app/features/preprints/store/preprint/preprint.actions.ts @@ -23,3 +23,7 @@ export class FetchPreprintFile { export class FetchPreprintFileVersions { static readonly type = '[Preprint] Fetch Preprint File Versions'; } + +export class ResetState { + static readonly type = '[Preprint] Reset State'; +} diff --git a/src/app/features/preprints/store/preprint/preprint.state.ts b/src/app/features/preprints/store/preprint/preprint.state.ts index e0253317a..53da47af4 100644 --- a/src/app/features/preprints/store/preprint/preprint.state.ts +++ b/src/app/features/preprints/store/preprint/preprint.state.ts @@ -10,7 +10,13 @@ import { handleSectionError } from '@core/handlers'; import { PreprintsService } from '@osf/features/preprints/services'; import { FilesService } from '@shared/services'; -import { FetchMyPreprints, FetchPreprintById, FetchPreprintFile, FetchPreprintFileVersions } from './preprint.actions'; +import { + FetchMyPreprints, + FetchPreprintById, + FetchPreprintFile, + FetchPreprintFileVersions, + ResetState, +} from './preprint.actions'; import { DefaultState, PreprintStateModel } from './preprint.model'; @State({ @@ -85,4 +91,9 @@ export class PreprintState { catchError((error) => handleSectionError(ctx, 'fileVersions', error)) ); } + + @Action(ResetState) + resetState(ctx: StateContext) { + ctx.setState(patch({ ...DefaultState })); + } } From 8e9d31dab472975b6cb21c1fa4269df7fc685b12 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 13:57:34 +0300 Subject: [PATCH 07/12] feat(preprint-details): Partly implemented share-and-download section --- src/app/core/models/json-api.model.ts | 4 ++ .../share-and-download.component.html | 41 +++++++++++++++ .../share-and-download.component.scss | 22 ++++++++ .../share-and-download.component.spec.ts | 22 ++++++++ .../share-and-download.component.ts | 40 +++++++++++++++ .../preprints/mappers/preprints.mapper.ts | 51 ++++++++++++++++++- .../models/preprint-json-api.models.ts | 7 +++ .../preprints/models/preprint.models.ts | 6 +++ .../preprint-details.component.html | 10 ++-- .../preprint-details.component.ts | 7 +-- .../preprints/services/preprints.service.ts | 23 ++++++++- .../store/preprint/preprint.state.ts | 4 +- 12 files changed, 224 insertions(+), 13 deletions(-) create mode 100644 src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.html create mode 100644 src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.scss create mode 100644 src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.spec.ts create mode 100644 src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.ts diff --git a/src/app/core/models/json-api.model.ts b/src/app/core/models/json-api.model.ts index 8f52461e3..cdf0c28f6 100644 --- a/src/app/core/models/json-api.model.ts +++ b/src/app/core/models/json-api.model.ts @@ -3,6 +3,10 @@ export interface JsonApiResponse { included?: Included; } +export interface JsonApiResponseWithMeta extends JsonApiResponse { + meta: Meta; +} + export interface JsonApiResponseWithPaging extends JsonApiResponse { links: { meta: MetaJsonApi; diff --git a/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.html b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.html new file mode 100644 index 000000000..964920e55 --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.html @@ -0,0 +1,41 @@ + +
+ +
+ @if (preprint()) { + Download preprint + } + + @if (metrics()) { +
+ Views: {{ metrics()!.views }} + | + Downloads: {{ metrics()!.downloads }} +
+ } + + @if (isPreprintLoading()) { + + + } +
+ + +
+
diff --git a/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.scss b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.scss new file mode 100644 index 000000000..6c6740f9f --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.scss @@ -0,0 +1,22 @@ +@use "assets/styles/mixins" as mix; +@use "assets/styles/variables" as var; + +.social-link { + background-color: var(--pr-blue-1); + border-radius: mix.rem(8px); + color: var(--white); + padding: mix.rem(7px); + width: mix.rem(36px); + height: mix.rem(36px); + + &:hover { + background-color: var(--pr-blue-3); + text-decoration: none; + } +} + +.card { + @media (max-width: var.$breakpoint-sm) { + --p-card-body-padding: 0.75rem; + } +} diff --git a/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.spec.ts b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.spec.ts new file mode 100644 index 000000000..f5f713b5f --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShareAndDownloadComponent } from './share-and-download.component'; + +describe('ShareAndDownlaodComponent', () => { + let component: ShareAndDownloadComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ShareAndDownloadComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ShareAndDownloadComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.ts b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.ts new file mode 100644 index 000000000..1eb309acc --- /dev/null +++ b/src/app/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component.ts @@ -0,0 +1,40 @@ +import { select } from '@ngxs/store'; + +import { ButtonDirective } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Skeleton } from 'primeng/skeleton'; + +import { ChangeDetectionStrategy, Component, computed } from '@angular/core'; + +import { PreprintSelectors } from '@osf/features/preprints/store/preprint'; +import { IconComponent } from '@shared/components'; + +import { environment } from 'src/environments/environment'; + +@Component({ + selector: 'osf-preprint-share-and-download', + imports: [Card, IconComponent, Skeleton, ButtonDirective], + templateUrl: './share-and-download.component.html', + styleUrl: './share-and-download.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ShareAndDownloadComponent { + preprint = select(PreprintSelectors.getPreprint); + isPreprintLoading = select(PreprintSelectors.isPreprintLoading); + + metrics = computed(() => { + const preprint = this.preprint(); + + if (!preprint) return null; + + return preprint.metrics!; + }); + + downloadLink = computed(() => { + const preprint = this.preprint(); + + if (!preprint) return '#'; + + return `${environment.webUrl}/${this.preprint()?.id}/download/`; + }); +} diff --git a/src/app/features/preprints/mappers/preprints.mapper.ts b/src/app/features/preprints/mappers/preprints.mapper.ts index 1218d3514..1659e30df 100644 --- a/src/app/features/preprints/mappers/preprints.mapper.ts +++ b/src/app/features/preprints/mappers/preprints.mapper.ts @@ -1,8 +1,9 @@ -import { ApiData, JsonApiResponseWithPaging } from '@core/models'; +import { ApiData, JsonApiResponseWithMeta, JsonApiResponseWithPaging } from '@core/models'; import { Preprint, PreprintAttributesJsonApi, PreprintEmbedsJsonApi, + PreprintMetaJsonApi, PreprintRelationshipsJsonApi, PreprintShortInfoWithTotalCount, } from '@osf/features/preprints/models'; @@ -66,6 +67,54 @@ export class PreprintsMapper { }; } + static fromPreprintWithEmbedsJsonApi( + response: JsonApiResponseWithMeta< + ApiData, + PreprintMetaJsonApi, + null + > + ): Preprint { + const data = response.data; + const meta = response.meta; + return { + id: data.id, + dateCreated: data.attributes.date_created, + dateModified: data.attributes.date_modified, + title: data.attributes.title, + description: data.attributes.description, + doi: data.attributes.doi, + customPublicationCitation: data.attributes.custom_publication_citation, + originalPublicationDate: data.attributes.original_publication_date, + isPublished: data.attributes.is_published, + tags: data.attributes.tags, + isPublic: data.attributes.public, + version: data.attributes.version, + isLatestVersion: data.attributes.is_latest_version, + primaryFileId: data.relationships.primary_file?.data?.id || null, + nodeId: data.relationships.node?.data?.id, + licenseId: data.relationships.license?.data?.id || null, + licenseOptions: data.attributes.license_record + ? { + year: data.attributes.license_record.year, + copyrightHolders: data.attributes.license_record.copyright_holders.join(','), + } + : null, + hasCoi: data.attributes.has_coi, + coiStatement: data.attributes.conflict_of_interest_statement, + hasDataLinks: data.attributes.has_data_links, + dataLinks: data.attributes.data_links, + whyNoData: data.attributes.why_no_data, + hasPreregLinks: data.attributes.has_prereg_links, + whyNoPrereg: data.attributes.why_no_prereg, + preregLinks: data.attributes.prereg_links, + preregLinkInfo: data.attributes.prereg_link_info, + metrics: { + downloads: meta.metrics.downloads, + views: meta.metrics.views, + }, + }; + } + static toSubmitPreprintPayload(preprintId: string) { return { data: { 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 e07a059a1..d416d463b 100644 --- a/src/app/features/preprints/models/preprint-json-api.models.ts +++ b/src/app/features/preprints/models/preprint-json-api.models.ts @@ -66,3 +66,10 @@ export interface PreprintEmbedsJsonApi { data: ContributorResponse[]; }; } + +export interface PreprintMetaJsonApi { + metrics: { + downloads: number; + views: number; + }; +} diff --git a/src/app/features/preprints/models/preprint.models.ts b/src/app/features/preprints/models/preprint.models.ts index 90cdabde7..b6282a0ad 100644 --- a/src/app/features/preprints/models/preprint.models.ts +++ b/src/app/features/preprints/models/preprint.models.ts @@ -29,6 +29,7 @@ export interface Preprint { whyNoPrereg: StringOrNull; preregLinks: string[]; preregLinkInfo: PreregLinkInfo | null; + metrics?: PreprintMetrics | null; } export interface PreprintFilesLinks { @@ -48,3 +49,8 @@ export interface PreprintShortInfoWithTotalCount { data: PreprintShortInfo[]; totalCount: number; } + +export interface PreprintMetrics { + downloads: number; + views: number; +} diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html index bbcf9a070..d89fec168 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.html +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.html @@ -1,8 +1,8 @@
@if (isPreprintProviderLoading() || isPreprintLoading()) { - - + + } @else { Provider Logo

{{ preprint()!.title }}

@@ -23,7 +23,7 @@

{{ preprint()!.title }}

- +

Status banner here

@@ -33,9 +33,7 @@

{{ preprint()!.title }}

- -

section with download

-
+

section with abstract

diff --git a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts index ddd746459..5a4d8224d 100644 --- a/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts +++ b/src/app/features/preprints/pages/preprint-details/preprint-details.component.ts @@ -11,13 +11,14 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; import { PreprintFileSectionComponent } from '@osf/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component'; -import { FetchPreprintById, PreprintSelectors } from '@osf/features/preprints/store/preprint'; +import { ShareAndDownloadComponent } from '@osf/features/preprints/components/preprint-details/share-and-downlaod/share-and-download.component'; +import { FetchPreprintById, PreprintSelectors, ResetState } from '@osf/features/preprints/store/preprint'; import { GetPreprintProviderById, PreprintProvidersSelectors } from '@osf/features/preprints/store/preprint-providers'; -import { CreateNewVersion, PreprintStepperSelectors, ResetState } from '@osf/features/preprints/store/preprint-stepper'; +import { CreateNewVersion, PreprintStepperSelectors } from '@osf/features/preprints/store/preprint-stepper'; @Component({ selector: 'osf-preprint-details', - imports: [Skeleton, PreprintFileSectionComponent, Card, Button], + imports: [Skeleton, PreprintFileSectionComponent, Card, Button, ShareAndDownloadComponent], templateUrl: './preprint-details.component.html', styleUrl: './preprint-details.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 4bc3f9450..650c1e990 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -3,13 +3,14 @@ import { map, Observable } from 'rxjs'; import { inject, Injectable } from '@angular/core'; import { JsonApiService } from '@core/services'; -import { ApiData, JsonApiResponse, JsonApiResponseWithPaging } from '@osf/core/models'; +import { ApiData, JsonApiResponse, JsonApiResponseWithMeta, JsonApiResponseWithPaging } from '@osf/core/models'; import { preprintSortFieldMap } from '@osf/features/preprints/constants'; import { PreprintsMapper } from '@osf/features/preprints/mappers'; import { Preprint, PreprintAttributesJsonApi, PreprintEmbedsJsonApi, + PreprintMetaJsonApi, PreprintRelationshipsJsonApi, } from '@osf/features/preprints/models'; import { SearchFilters } from '@shared/models'; @@ -66,6 +67,26 @@ export class PreprintsService { ); } + getByIdWithEmbeds(id: string) { + const params = { + 'metrics[views]': 'total', + 'metrics[downloads]': 'total', + }; + return this.jsonApiService + .get< + JsonApiResponseWithMeta< + ApiData, + PreprintMetaJsonApi, + null + > + >(`${environment.apiUrl}/preprints/${id}/`, params) + .pipe( + map((response) => { + return PreprintsMapper.fromPreprintWithEmbedsJsonApi(response); + }) + ); + } + deletePreprint(id: string) { return this.jsonApiService.delete(`${environment.apiUrl}/preprints/${id}/`); } diff --git a/src/app/features/preprints/store/preprint/preprint.state.ts b/src/app/features/preprints/store/preprint/preprint.state.ts index 53da47af4..59c421a57 100644 --- a/src/app/features/preprints/store/preprint/preprint.state.ts +++ b/src/app/features/preprints/store/preprint/preprint.state.ts @@ -50,10 +50,10 @@ export class PreprintState { } @Action(FetchPreprintById) - getPreprintById(ctx: StateContext, action: FetchPreprintById) { + fetchPreprintById(ctx: StateContext, action: FetchPreprintById) { ctx.setState(patch({ preprint: patch({ isLoading: true }) })); - return this.preprintsService.getById(action.id).pipe( + return this.preprintsService.getByIdWithEmbeds(action.id).pipe( tap((preprint) => { ctx.setState(patch({ preprint: patch({ isLoading: false, data: preprint }) })); this.store.dispatch(new FetchPreprintFile()); From fbd9504796a664af4ff46ae212e7bf5d2ad85ed0 Mon Sep 17 00:00:00 2001 From: Roma Date: Thu, 17 Jul 2025 13:58:38 +0300 Subject: [PATCH 08/12] style(preprint-details): Fixed styles for file section --- .../preprint-file-section.component.html | 9 +++++++-- .../preprint-file-section.component.scss | 10 ++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html index a6fc9a467..120fb39d1 100644 --- a/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html +++ b/src/app/features/preprints/components/preprint-details/preprint-file-section/preprint-file-section.component.html @@ -1,4 +1,9 @@ -
+
@if (safeLink()) {