Skip to content

Commit 26fc96d

Browse files
authored
Feat - Submit preprint (#128)
* feat(submit-preprint-state): Implemented state for submit preprint * feat(select-preprint-state): Implemented select service page with api integration * feat(select-preprint-service): Enhance UI with dynamic translations * feat(select-preprint-service): Enhance UI with dynamic translations * fix(preprint-provider-hero): Added skeleton for search input while preprint provider is loading * fix(preprints-styles): Fixed styles due to merge conflicts * feat(nav-menu): Added subroutes for preprints * fix(preprints-search-input): Changed placeholder * fix(comments): Fixed comments * fix(preprints-help-dialog): Fixed url to help guide * feat(preprints-favicon): Implemented updating browser tab favicon and title with preprint branding * fix(select): remove duplicate style * feat(stepper): Implemented shared component * feat(submit-preprint-stepper): Created parent component for submit component stepper * feat(submit-preprint-first-step): Partly implemented first step * feat(submit-preprint-first-step): Finished title and abstract step * feat(submit-preprint-file-step): Partly implemented layout * feat(submit-preprint-file-step): Implemented file upload from computer * feat(submit-preprint-file-step): Partly implemented select from osf project tab * refactor(files-tree): Made files-tree more reusable by passing currentFolder and handling file click * feat(submit-preprint-file-step): Implemented project files viewing * refactor(submit-preprint-stepper): Fixed comments
1 parent 5410d2b commit 26fc96d

File tree

49 files changed

+1621
-71
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1621
-71
lines changed

src/app/features/preprints/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { BrowseBySubjectsComponent } from './browse-by-subjects/browse-by-subjects.component';
22
export { PreprintServicesComponent } from './preprint-services/preprint-services.component';
3+
export { TitleAndAbstractStepComponent } from './submit-steps/title-and-abstract-step/title-and-abstract-step.component';
34
export { AdvisoryBoardComponent } from '@osf/features/preprints/components/advisory-board/advisory-board.component';
45
export { PreprintsCreatorsFilterComponent } from '@osf/features/preprints/components/filters/preprints-creators-filter/preprints-creators-filter.component';
56
export { PreprintsDateCreatedFilterComponent } from '@osf/features/preprints/components/filters/preprints-date-created-filter/preprints-date-created-filter.component';

src/app/features/preprints/components/preprint-provider-hero/preprint-provider-hero.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ <h1 class="preprint-provider-name">{{ preprintProvider()!.name }}</h1>
2222
class="ml-auto w-full md:w-auto preprints-branding-button"
2323
styleClass="w-full md:w-auto"
2424
[label]="'preprints.addPreprint' | translate: { preprintWord: preprintProvider()!.preprintWord } | titlecase"
25-
(click)="addPreprint()"
25+
[routerLink]="['/preprints', preprintProvider()!.id, 'submit']"
2626
/>
2727
}
2828
</div>

src/app/features/preprints/components/preprint-provider-hero/preprint-provider-hero.component.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,8 @@ export class PreprintProviderHeroComponent {
2929
searchControl = input<FormControl>(new FormControl());
3030
preprintProvider = input.required<PreprintProviderDetails | undefined>();
3131
isPreprintProviderLoading = input.required<boolean>();
32-
addPreprintClicked = output<void>();
3332
triggerSearch = output<string>();
3433

35-
addPreprint() {
36-
this.addPreprintClicked.emit();
37-
}
38-
3934
onTriggerSearch(value: string) {
4035
this.triggerSearch.emit(value);
4136
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<h2>File</h2>
2+
3+
<section class="m-t-12">
4+
<p class="line-height-3">Please Upload your Preprint</p>
5+
<p class="line-height-3">Note: You cannot switch options once a file is attached.</p>
6+
</section>
7+
8+
<section class="m-t-24 w-full flex flex-column gap-2 md:flex-row">
9+
<p-button
10+
[ngClass]="{
11+
active: selectedFileSource() === PreprintFileSource.Computer,
12+
}"
13+
class="file-source-button w-full"
14+
[styleClass]="selectedFileSource() ? 'w-full cursor-not-allowed' : 'w-full'"
15+
[label]="'Upload From Your Computer' | titlecase"
16+
severity="secondary"
17+
[disabled]="isFileSourceSelected()"
18+
[pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your computer.' : ''"
19+
tooltipPosition="top"
20+
(click)="selectFileSource(PreprintFileSource.Computer)"
21+
/>
22+
<p-button
23+
[ngClass]="{
24+
active: selectedFileSource() === PreprintFileSource.Project,
25+
}"
26+
class="file-source-button w-full"
27+
[styleClass]="isFileSourceSelected() ? 'w-full cursor-not-allowed' : 'w-full'"
28+
[label]="'Select From existing OSF project' | titlecase"
29+
severity="secondary"
30+
[disabled]="isFileSourceSelected()"
31+
[pTooltip]="isFileSourceSelected() ? 'Start a new preprint to attach a file from your project.' : ''"
32+
tooltipPosition="top"
33+
(click)="selectFileSource(PreprintFileSource.Project)"
34+
/>
35+
</section>
36+
37+
@if (isFileSourceSelected() && selectedFileSource() === PreprintFileSource.Computer) {
38+
<section class="m-t-48">
39+
@if (!fileUploadLink()) {
40+
<p-skeleton width="5rem" height="2rem" />
41+
} @else if (!preprintFiles().length) {
42+
<p-button
43+
outlined
44+
raised
45+
severity="success"
46+
[icon]="'fas fa-upload'"
47+
[label]="'Upload file'"
48+
(click)="fileInput.click()"
49+
/>
50+
51+
<input #fileInput type="file" class="hidden" (change)="onFileSelected($event)" />
52+
}
53+
</section>
54+
55+
<section>
56+
@if (arePreprintFilesLoading()) {
57+
<p-skeleton width="20rem" height="2rem" class="m-t-48" />
58+
} @else {
59+
@for (file of preprintFiles(); track file.id) {
60+
<section class="file-row">
61+
<div class="flex flex-row gap-2">
62+
<osf-icon [iconClass]="'fas fa-file'" />
63+
<p>{{ file.name }}</p>
64+
</div>
65+
66+
<p-button class="danger-icon-btn" icon="fas fa-trash" severity="danger" text />
67+
</section>
68+
}
69+
}
70+
</section>
71+
}
72+
73+
@if (isFileSourceSelected() && selectedFileSource() === PreprintFileSource.Project) {
74+
<p-card styleClass="m-t-48">
75+
<div>
76+
<p class="line-height-3">This will make your project public, if it is not already</p>
77+
<p class="line-height-3">The projects and components for which you have admin access are listed below.</p>
78+
79+
<p-select
80+
id="project-select"
81+
[options]="availableProjects()"
82+
optionLabel="name"
83+
optionValue="id"
84+
[formControl]="projectNameControl"
85+
[placeholder]="'Project Title'"
86+
class="w-6"
87+
[editable]="true"
88+
styleClass="m-t-24"
89+
appendTo="body"
90+
[loading]="areAvailableProjectsLoading()"
91+
(onChange)="selectProject($event)"
92+
[showClear]="false"
93+
/>
94+
</div>
95+
</p-card>
96+
97+
<section class="m-t-24">
98+
@if (selectedProjectId()) {
99+
<osf-files-tree
100+
[currentFolder]="currentFolder()"
101+
[files]="projectFiles()"
102+
[isLoading]="areProjectFilesLoading()"
103+
[projectId]="selectedProjectId()!"
104+
[actions]="filesTreeActions"
105+
(entryFileClicked)="selectProjectFile($event)"
106+
/>
107+
}
108+
</section>
109+
}
110+
111+
<section class="m-t-48 flex flex-row justify-content-end align-items-center gap-2">
112+
<p-button class="w-6rem" styleClass="w-full" label="Back" severity="info" (click)="backButtonClicked()" />
113+
<p-button class="w-9rem" styleClass="w-full" label="Next" (click)="nextButtonClicked()" />
114+
</section>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
@use "assets/styles/mixins" as mix;
2+
3+
.file-source-button {
4+
--p-button-secondary-border-color: var(--grey-2);
5+
--p-button-secondary-background: transparent;
6+
--p-button-secondary-hover-background: var(--bg-blue-3);
7+
--p-button-padding-y: 0.75rem;
8+
--p-button-secondary-color: var(--dark-blue-1);
9+
--p-button-secondary-hover-color: var(--dark-blue-1);
10+
11+
&.active {
12+
--p-button-secondary-background: var(--bg-blue-3);
13+
}
14+
}
15+
16+
.file-row {
17+
@include mix.flex-center-between;
18+
margin-top: mix.rem(48px);
19+
padding: mix.rem(6px) mix.rem(12px);
20+
border-bottom: 1px solid var(--grey-2);
21+
border-top: 1px solid var(--grey-2);
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { FileStepComponent } from './file-step.component';
4+
5+
describe('FileStepComponent', () => {
6+
let component: FileStepComponent;
7+
let fixture: ComponentFixture<FileStepComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [FileStepComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(FileStepComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
});
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { createDispatchMap, select } from '@ngxs/store';
2+
3+
import { Button } from 'primeng/button';
4+
import { Card } from 'primeng/card';
5+
import { DialogService } from 'primeng/dynamicdialog';
6+
import { Select, SelectChangeEvent } from 'primeng/select';
7+
import { Skeleton } from 'primeng/skeleton';
8+
import { Tooltip } from 'primeng/tooltip';
9+
10+
import { debounceTime, distinctUntilChanged, EMPTY, Observable } from 'rxjs';
11+
12+
import { NgClass, TitleCasePipe } from '@angular/common';
13+
import {
14+
ChangeDetectionStrategy,
15+
Component,
16+
computed,
17+
DestroyRef,
18+
HostListener,
19+
inject,
20+
OnInit,
21+
output,
22+
signal,
23+
} from '@angular/core';
24+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
25+
import { FormControl, ReactiveFormsModule } from '@angular/forms';
26+
27+
import { StringOrNull } from '@core/helpers';
28+
import { PreprintFileSource } from '@osf/features/preprints/enums';
29+
import {
30+
GetAvailableProjects,
31+
GetPreprintFilesLinks,
32+
GetProjectFiles,
33+
GetProjectFilesByLink,
34+
SetSelectedPreprintFileSource,
35+
SubmitPreprintSelectors,
36+
UploadFile,
37+
} from '@osf/features/preprints/store/submit-preprint';
38+
import { FilesTreeActions } from '@osf/features/project/files/models';
39+
import { FilesTreeComponent, IconComponent } from '@shared/components';
40+
import { OsfFile } from '@shared/models';
41+
42+
@Component({
43+
selector: 'osf-file-step',
44+
imports: [
45+
Button,
46+
TitleCasePipe,
47+
NgClass,
48+
Tooltip,
49+
Skeleton,
50+
IconComponent,
51+
Card,
52+
Select,
53+
ReactiveFormsModule,
54+
FilesTreeComponent,
55+
],
56+
templateUrl: './file-step.component.html',
57+
styleUrl: './file-step.component.scss',
58+
providers: [DialogService],
59+
changeDetection: ChangeDetectionStrategy.OnPush,
60+
})
61+
export class FileStepComponent implements OnInit {
62+
private actions = createDispatchMap({
63+
setSelectedFileSource: SetSelectedPreprintFileSource,
64+
getPreprintFilesLinks: GetPreprintFilesLinks,
65+
uploadFile: UploadFile,
66+
getAvailableProjects: GetAvailableProjects,
67+
getFilesForSelectedProject: GetProjectFiles,
68+
getProjectFilesByLink: GetProjectFilesByLink,
69+
});
70+
private destroyRef = inject(DestroyRef);
71+
72+
readonly PreprintFileSource = PreprintFileSource;
73+
74+
providerId = select(SubmitPreprintSelectors.getSelectedProviderId);
75+
selectedFileSource = select(SubmitPreprintSelectors.getSelectedFileSource);
76+
fileUploadLink = select(SubmitPreprintSelectors.getUploadLink);
77+
preprintFiles = select(SubmitPreprintSelectors.getPreprintFiles);
78+
arePreprintFilesLoading = select(SubmitPreprintSelectors.arePreprintFilesLoading);
79+
availableProjects = select(SubmitPreprintSelectors.getAvailableProjects);
80+
areAvailableProjectsLoading = select(SubmitPreprintSelectors.areAvailableProjectsLoading);
81+
projectFiles = select(SubmitPreprintSelectors.getProjectFiles);
82+
areProjectFilesLoading = select(SubmitPreprintSelectors.areProjectFilesLoading);
83+
selectedProjectId = signal<StringOrNull>(null);
84+
currentFolder = signal<OsfFile | null>(null);
85+
86+
projectNameControl = new FormControl<StringOrNull>(null);
87+
88+
filesTreeActions: FilesTreeActions = {
89+
setCurrentFolder: (folder: OsfFile | null): Observable<void> => {
90+
this.currentFolder.set(folder);
91+
return EMPTY;
92+
},
93+
getFiles: (filesLink: string): Observable<void> => {
94+
return this.actions.getProjectFilesByLink(filesLink);
95+
},
96+
getRootFolderFiles: (projectId: string): Observable<void> => {
97+
return this.actions.getFilesForSelectedProject(projectId);
98+
},
99+
};
100+
101+
nextClicked = output<void>();
102+
103+
isFileSourceSelected = computed(() => {
104+
return this.selectedFileSource() !== PreprintFileSource.None;
105+
});
106+
107+
ngOnInit() {
108+
this.actions.getPreprintFilesLinks();
109+
110+
this.projectNameControl.valueChanges
111+
.pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
112+
.subscribe((value) => {
113+
this.selectedProjectId.set(value);
114+
this.actions.getAvailableProjects(value);
115+
});
116+
}
117+
118+
selectFileSource(fileSource: PreprintFileSource) {
119+
this.actions.setSelectedFileSource(fileSource);
120+
121+
if (fileSource === PreprintFileSource.Project) {
122+
this.actions.getAvailableProjects(null);
123+
}
124+
}
125+
126+
backButtonClicked() {
127+
//[RNi] TODO: implement logic of going back to the previous step
128+
}
129+
130+
nextButtonClicked() {
131+
this.nextClicked.emit();
132+
}
133+
134+
onFileSelected(event: Event): void {
135+
const input = event.target as HTMLInputElement;
136+
const file = input.files?.[0];
137+
if (!file) return;
138+
139+
this.actions.uploadFile(file);
140+
}
141+
142+
@HostListener('window:beforeunload', ['$event'])
143+
public onBeforeUnload($event: BeforeUnloadEvent): boolean {
144+
$event.preventDefault();
145+
return false;
146+
}
147+
148+
selectProject(event: SelectChangeEvent) {
149+
if (!(event.originalEvent instanceof PointerEvent)) {
150+
return;
151+
}
152+
153+
this.actions.getFilesForSelectedProject(event.value);
154+
}
155+
156+
selectProjectFile(file: OsfFile) {
157+
//[RNi] TODO: implement logic of linking preprint to that file
158+
}
159+
}

0 commit comments

Comments
 (0)