Skip to content

Feat - Submit preprint #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
db49641
feat(submit-preprint-state): Implemented state for submit preprint
rrromchIk Jun 13, 2025
14acced
feat(select-preprint-state): Implemented select service page with api…
rrromchIk Jun 13, 2025
6d65b1b
feat(select-preprint-service): Enhance UI with dynamic translations
rrromchIk Jun 13, 2025
4efc298
feat(select-preprint-service): Enhance UI with dynamic translations
rrromchIk Jun 13, 2025
c5d0ea5
Merge remote-tracking branch 'origin/feat/submit-preprint' into feat/…
rrromchIk Jun 16, 2025
5bb72fb
fix(preprint-provider-hero): Added skeleton for search input while pr…
rrromchIk Jun 16, 2025
0a358c7
fix(preprints-styles): Fixed styles due to merge conflicts
rrromchIk Jun 16, 2025
70046df
Merge branch 'main' into feat/submit-preprint
rrromchIk Jun 16, 2025
7a34b3c
feat(nav-menu): Added subroutes for preprints
rrromchIk Jun 16, 2025
3bfc768
fix(preprints-search-input): Changed placeholder
rrromchIk Jun 16, 2025
f90c8c7
fix(comments): Fixed comments
rrromchIk Jun 16, 2025
964f310
fix(preprints-help-dialog): Fixed url to help guide
rrromchIk Jun 16, 2025
8aef899
feat(preprints-favicon): Implemented updating browser tab favicon and…
rrromchIk Jun 16, 2025
e4281a9
Merge branch 'main' into feat/submit-preprint
rrromchIk Jun 16, 2025
dffc84c
fix(select): remove duplicate style
rrromchIk Jun 16, 2025
2c7a628
feat(stepper): Implemented shared component
rrromchIk Jun 17, 2025
3bc707a
feat(submit-preprint-stepper): Created parent component for submit co…
rrromchIk Jun 17, 2025
48caa83
feat(submit-preprint-first-step): Partly implemented first step
rrromchIk Jun 17, 2025
7c42589
feat(submit-preprint-first-step): Finished title and abstract step
rrromchIk Jun 18, 2025
e17faa7
feat(submit-preprint-file-step): Partly implemented layout
rrromchIk Jun 19, 2025
d8e9b78
Merge branch 'main' into feat/submit-preprint
rrromchIk Jun 19, 2025
13b4175
feat(submit-preprint-file-step): Implemented file upload from computer
rrromchIk Jun 19, 2025
3ca76de
feat(submit-preprint-file-step): Partly implemented select from osf p…
rrromchIk Jun 20, 2025
17a7b5e
Merge branch 'main' into feat/submit-preprint
rrromchIk Jun 20, 2025
53cca7e
refactor(files-tree): Made files-tree more reusable by passing curren…
rrromchIk Jun 20, 2025
68867da
feat(submit-preprint-file-step): Implemented project files viewing
rrromchIk Jun 20, 2025
e53edf5
Merge branch 'main' into feat/submit-preprint
rrromchIk Jun 20, 2025
7c18f45
refactor(submit-preprint-stepper): Fixed comments
rrromchIk Jun 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/features/preprints/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { BrowseBySubjectsComponent } from './browse-by-subjects/browse-by-subjects.component';
export { PreprintServicesComponent } from './preprint-services/preprint-services.component';
export { TitleAndAbstractStepComponent } from './submit-steps/title-and-abstract-step/title-and-abstract-step.component';
export { AdvisoryBoardComponent } from '@osf/features/preprints/components/advisory-board/advisory-board.component';
export { PreprintsCreatorsFilterComponent } from '@osf/features/preprints/components/filters/preprints-creators-filter/preprints-creators-filter.component';
export { PreprintsDateCreatedFilterComponent } from '@osf/features/preprints/components/filters/preprints-date-created-filter/preprints-date-created-filter.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ <h1 class="preprint-provider-name">{{ preprintProvider()!.name }}</h1>
class="ml-auto w-full md:w-auto preprints-branding-button"
styleClass="w-full md:w-auto"
[label]="'preprints.addPreprint' | translate: { preprintWord: preprintProvider()!.preprintWord } | titlecase"
(click)="addPreprint()"
[routerLink]="['/preprints', preprintProvider()!.id, 'submit']"
/>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,8 @@ export class PreprintProviderHeroComponent {
searchControl = input<FormControl>(new FormControl());
preprintProvider = input.required<PreprintProviderDetails | undefined>();
isPreprintProviderLoading = input.required<boolean>();
addPreprintClicked = output<void>();
triggerSearch = output<string>();

addPreprint() {
this.addPreprintClicked.emit();
}

onTriggerSearch(value: string) {
this.triggerSearch.emit(value);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<h2>File</h2>

<section class="m-t-12">
<p class="line-height-3">Please Upload your Preprint</p>
<p class="line-height-3">Note: You cannot switch options once a file is attached.</p>
</section>

<section class="m-t-24 w-full flex flex-column gap-2 md:flex-row">
<p-button
[ngClass]="{
active: selectedFileSource() === PreprintFileSource.Computer,
}"
class="file-source-button w-full"
[styleClass]="selectedFileSource() ? 'w-full cursor-not-allowed' : 'w-full'"
[label]="'Upload From Your Computer' | titlecase"
severity="secondary"
[disabled]="isFileSourceSelected()"
[pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your computer.' : ''"
tooltipPosition="top"
(click)="selectFileSource(PreprintFileSource.Computer)"
/>
<p-button
[ngClass]="{
active: selectedFileSource() === PreprintFileSource.Project,
}"
class="file-source-button w-full"
[styleClass]="isFileSourceSelected() ? 'w-full cursor-not-allowed' : 'w-full'"
[label]="'Select From existing OSF project' | titlecase"
severity="secondary"
[disabled]="isFileSourceSelected()"
[pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your project.' : ''"
tooltipPosition="top"
(click)="selectFileSource(PreprintFileSource.Project)"
/>
</section>

@if (isFileSourceSelected() && selectedFileSource() === PreprintFileSource.Computer) {
<section class="m-t-48">
@if (!fileUploadLink()) {
<p-skeleton width="5rem" height="2rem" />
} @else if (!preprintFiles().length) {
<p-button
outlined
raised
severity="success"
[icon]="'fas fa-upload'"
[label]="'Upload file'"
(click)="fileInput.click()"
/>

<input #fileInput type="file" class="hidden" (change)="onFileSelected($event)" />
}
</section>

<section>
@if (arePreprintFilesLoading()) {
<p-skeleton width="20rem" height="2rem" class="m-t-48" />
} @else {
@for (file of preprintFiles(); track file.id) {
<section class="file-row">
<div class="flex flex-row gap-2">
<osf-icon [iconClass]="'fas fa-file'" />
<p>{{ file.name }}</p>
</div>

<p-button class="danger-icon-btn" icon="fas fa-trash" severity="danger" text />
</section>
}
}
</section>
}

@if (isFileSourceSelected() && selectedFileSource() === PreprintFileSource.Project) {
<p-card styleClass="m-t-48">
<div>
<p class="line-height-3">This will make your project public, if it is not already</p>
<p class="line-height-3">The projects and components for which you have admin access are listed below.</p>

<p-select
id="project-select"
[options]="availableProjects()"
optionLabel="name"
optionValue="id"
[formControl]="projectNameControl"
[placeholder]="'Project Title'"
class="w-6"
[editable]="true"
styleClass="m-t-24"
appendTo="body"
[loading]="areAvailableProjectsLoading()"
(onChange)="selectProject($event)"
[showClear]="false"
/>
</div>
</p-card>

<section class="m-t-24">
@if (selectedProjectId()) {
<osf-files-tree
[currentFolder]="currentFolder()"
[files]="projectFiles()"
[isLoading]="areProjectFilesLoading()"
[projectId]="selectedProjectId()!"
[actions]="filesTreeActions"
(entryFileClicked)="selectProjectFile($event)"
/>
}
</section>
}

<section class="m-t-48 flex flex-row justify-content-end align-items-center gap-2">
<p-button class="w-6rem" styleClass="w-full" label="Back" severity="info" (click)="backButtonClicked()" />
<p-button class="w-9rem" styleClass="w-full" label="Next" (click)="nextButtonClicked()" />
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@use "assets/styles/mixins" as mix;

.file-source-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);
}
}

.file-row {
@include mix.flex-center-between;
margin-top: mix.rem(48px);
padding: mix.rem(6px) mix.rem(12px);
border-bottom: 1px solid var(--grey-2);
border-top: 1px solid var(--grey-2);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { FileStepComponent } from './file-step.component';

describe('FileStepComponent', () => {
let component: FileStepComponent;
let fixture: ComponentFixture<FileStepComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [FileStepComponent],
}).compileComponents();

fixture = TestBed.createComponent(FileStepComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { createDispatchMap, select } from '@ngxs/store';

import { Button } from 'primeng/button';
import { Card } from 'primeng/card';
import { DialogService } from 'primeng/dynamicdialog';
import { Select, SelectChangeEvent } from 'primeng/select';
import { Skeleton } from 'primeng/skeleton';
import { Tooltip } from 'primeng/tooltip';

import { debounceTime, distinctUntilChanged, EMPTY, Observable } from 'rxjs';

import { NgClass, TitleCasePipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
computed,
DestroyRef,
HostListener,
inject,
OnInit,
output,
signal,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule } from '@angular/forms';

import { StringOrNull } from '@core/helpers';
import { PreprintFileSource } from '@osf/features/preprints/enums';
import {
GetAvailableProjects,
GetPreprintFilesLinks,
GetProjectFiles,
GetProjectFilesByLink,
SetSelectedPreprintFileSource,
SubmitPreprintSelectors,
UploadFile,
} from '@osf/features/preprints/store/submit-preprint';
import { FilesTreeActions } from '@osf/features/project/files/models';
import { FilesTreeComponent, IconComponent } from '@shared/components';
import { OsfFile } from '@shared/models';

@Component({
selector: 'osf-file-step',
imports: [
Button,
TitleCasePipe,
NgClass,
Tooltip,
Skeleton,
IconComponent,
Card,
Select,
ReactiveFormsModule,
FilesTreeComponent,
],
templateUrl: './file-step.component.html',
styleUrl: './file-step.component.scss',
providers: [DialogService],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileStepComponent implements OnInit {
private actions = createDispatchMap({
setSelectedFileSource: SetSelectedPreprintFileSource,
getPreprintFilesLinks: GetPreprintFilesLinks,
uploadFile: UploadFile,
getAvailableProjects: GetAvailableProjects,
getFilesForSelectedProject: GetProjectFiles,
getProjectFilesByLink: GetProjectFilesByLink,
});
private destroyRef = inject(DestroyRef);

readonly PreprintFileSource = PreprintFileSource;

providerId = select(SubmitPreprintSelectors.getSelectedProviderId);
selectedFileSource = select(SubmitPreprintSelectors.getSelectedFileSource);
fileUploadLink = select(SubmitPreprintSelectors.getUploadLink);
preprintFiles = select(SubmitPreprintSelectors.getPreprintFiles);
arePreprintFilesLoading = select(SubmitPreprintSelectors.arePreprintFilesLoading);
availableProjects = select(SubmitPreprintSelectors.getAvailableProjects);
areAvailableProjectsLoading = select(SubmitPreprintSelectors.areAvailableProjectsLoading);
projectFiles = select(SubmitPreprintSelectors.getProjectFiles);
areProjectFilesLoading = select(SubmitPreprintSelectors.areProjectFilesLoading);
selectedProjectId = signal<StringOrNull>(null);
currentFolder = signal<OsfFile | null>(null);

projectNameControl = new FormControl<StringOrNull>(null);

filesTreeActions: FilesTreeActions = {
setCurrentFolder: (folder: OsfFile | null): Observable<void> => {
this.currentFolder.set(folder);
return EMPTY;
},
getFiles: (filesLink: string): Observable<void> => {
return this.actions.getProjectFilesByLink(filesLink);
},
getRootFolderFiles: (projectId: string): Observable<void> => {
return this.actions.getFilesForSelectedProject(projectId);
},
};

nextClicked = output<void>();

isFileSourceSelected = computed(() => {
return this.selectedFileSource() !== PreprintFileSource.None;
});

ngOnInit() {
this.actions.getPreprintFilesLinks();

this.projectNameControl.valueChanges
.pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
.subscribe((value) => {
this.selectedProjectId.set(value);
this.actions.getAvailableProjects(value);
});
}

selectFileSource(fileSource: PreprintFileSource) {
this.actions.setSelectedFileSource(fileSource);

if (fileSource === PreprintFileSource.Project) {
this.actions.getAvailableProjects(null);
}
}

backButtonClicked() {
//[RNi] TODO: implement logic of going back to the previous step
}

nextButtonClicked() {
this.nextClicked.emit();
}

onFileSelected(event: Event): void {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (!file) return;

this.actions.uploadFile(file);
}

@HostListener('window:beforeunload', ['$event'])
public onBeforeUnload($event: BeforeUnloadEvent): boolean {
$event.preventDefault();
return false;
}

selectProject(event: SelectChangeEvent) {
if (!(event.originalEvent instanceof PointerEvent)) {
return;
}

this.actions.getFilesForSelectedProject(event.value);
}

selectProjectFile(file: OsfFile) {
//[RNi] TODO: implement logic of linking preprint to that file
}
}
Loading