From 2750352a060f0eb013aca72c57dbd21cc076c362 Mon Sep 17 00:00:00 2001
From: nsemets
Date: Fri, 6 Nov 2026 17:44:47 +0200
Subject: [PATCH 1/3] fix(preprint-file): added delete file logic
---
.../file-step/file-step.component.html | 112 ++++----
.../file-step/file-step.component.spec.ts | 243 +++++++++++-------
.../stepper/file-step/file-step.component.ts | 110 +++++---
.../preprint-stepper.actions.ts | 6 +-
.../preprint-stepper.state.ts | 38 ++-
5 files changed, 302 insertions(+), 207 deletions(-)
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 e11ec2ac7..4d4c4f14c 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
@@ -10,18 +10,21 @@ {{ 'preprints.preprintStepper.file.title' | translate }}
{{ 'preprints.preprintStepper.file.note' | translate }}
-@if (!preprintHasPrimaryFile() || versionFileMode()) {
+@if (!preprintHasPrimaryFile()) {
@@ -34,8 +37,8 @@ {{ 'preprints.preprintStepper.file.title' | translate }}
[styleClass]="isFileSourceSelected() ? 'w-full cursor-not-allowed' : 'w-full'"
[label]="'preprints.preprintStepper.file.selectFromProject' | translate"
severity="secondary"
- [disabled]="isFileSourceSelected() || versionFileMode()"
- [pTooltip]="'preprints.preprintStepper.file.tooltips.projectDisabled' | translate"
+ [disabled]="isFileSourceSelected()"
+ [pTooltip]="isFileSourceSelected() ? ('preprints.preprintStepper.file.tooltips.projectDisabled' | translate) : ''"
tooltipPosition="top"
(onClick)="selectFileSource(PreprintFileSource.Project)"
/>
@@ -46,13 +49,13 @@ {{ 'preprints.preprintStepper.file.title' | translate }}
@if (!fileUploadLink()) {
- } @else if ((!preprintFile() && !isPreprintFileLoading()) || versionFileMode()) {
+ } @else if (!preprintFile() && !isPreprintFileLoading()) {
@@ -63,39 +66,37 @@ {{ 'preprints.preprintStepper.file.title' | translate }}
@if (selectedFileSource() === PreprintFileSource.Project && !preprintFile() && !isPreprintFileLoading()) {
-
-
- {{ 'preprints.preprintStepper.projectSelection.description' | translate }}
-
-
- {{ 'preprints.preprintStepper.projectSelection.subDescription' | translate }}
-
+
+ {{ 'preprints.preprintStepper.projectSelection.description' | translate }}
+
+
+ {{ 'preprints.preprintStepper.projectSelection.subDescription' | translate }}
+
-
-
+
- @if (selectedProjectId()) {
+ @if (selectedProjectId() && currentFolder(); as folder) {
{{ 'preprints.preprintStepper.file.title' | translate }}
}
-@if (!versionFileMode()) {
-
- @if (isPreprintFileLoading()) {
-
- } @else if (preprintFile()) {
-
-
-
-
{{ preprintFile()!.name }}
-
-
- }
-
-}
+
+ @if (isPreprintFileLoading()) {
+
+ } @else if (preprintFile(); as file) {
+
+
+
+ @if (canDeletePrimaryFile()) {
+
+ }
+
+ }
+
{{ 'preprints.preprintStepper.file.title' | translate }}
class="w-6 md:w-9rem"
styleClass="w-full"
[label]="'common.buttons.next' | translate"
- [disabled]="!canProceedToNext()"
- [pTooltip]="
- !canProceedToNext() ? ('preprints.preprintStepper.common.validation.fillRequiredFields' | translate) : ''
- "
+ [disabled]="!preprintFile()"
+ [pTooltip]="!preprintFile() ? ('preprints.preprintStepper.common.validation.fillRequiredFields' | translate) : ''"
tooltipPosition="top"
(onClick)="nextButtonClicked()"
/>
diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
index 0c72697f5..9f54a2fc3 100644
--- a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
+++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
@@ -9,15 +9,15 @@ import { Mock } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PreprintFileSource } from '@osf/features/preprints/enums';
-import { PreprintModel, PreprintProviderDetails } from '@osf/features/preprints/models';
+import { PreprintProviderDetails } from '@osf/features/preprints/models';
import {
CopyFileFromProject,
+ DeletePreprintFile,
FetchAvailableProjects,
FetchPreprintFilesLinks,
FetchPreprintPrimaryFile,
FetchProjectFilesByLink,
PreprintStepperSelectors,
- ReuploadFile,
SetPreprintStepperCurrentFolder,
SetProjectRootFolder,
SetSelectedPreprintFileSource,
@@ -25,30 +25,58 @@ import {
} from '@osf/features/preprints/store/preprint-stepper';
import { FilesTreeComponent } from '@osf/shared/components/files-tree/files-tree.component';
import { IconComponent } from '@osf/shared/components/icon/icon.component';
-import { FileModel } from '@osf/shared/models/files/file.model';
import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
+import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { ToastService } from '@osf/shared/services/toast.service';
+import { FileModelMock } from '@testing/mocks/file.model.mock';
import { OSF_FILE_MOCK } from '@testing/mocks/osf-file.mock';
import { PREPRINT_MOCK } from '@testing/mocks/preprint.mock';
import { PREPRINT_PROVIDER_DETAILS_MOCK } from '@testing/mocks/preprint-provider-details';
import { provideOSFCore } from '@testing/osf.testing.provider';
-import { mergeSignalOverrides, provideMockStore, SignalOverride } from '@testing/providers/store-provider.mock';
+import {
+ CustomConfirmationServiceMock,
+ CustomConfirmationServiceMockType,
+} from '@testing/providers/custom-confirmation-provider.mock';
+import {
+ BaseSetupOverrides,
+ mergeSignalOverrides,
+ provideMockStore,
+ SignalOverride,
+} from '@testing/providers/store-provider.mock';
import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock';
import { FileStepComponent } from './file-step.component';
+interface SetupOverrides extends BaseSetupOverrides {
+ provider?: PreprintProviderDetails;
+ detectChanges?: boolean;
+}
+
describe('FileStepComponent', () => {
let component: FileStepComponent;
let fixture: ComponentFixture;
let store: Store;
let toastServiceMock: ToastServiceMockType;
+ let confirmationMock: CustomConfirmationServiceMockType;
const originalPointerEvent = (globalThis as unknown as { PointerEvent?: typeof Event }).PointerEvent;
const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK;
- const mockPreprint: PreprintModel = PREPRINT_MOCK;
- const mockProjectFiles: FileFolderModel[] = [OSF_FILE_MOCK];
- const mockPreprintFile: FileFolderModel = OSF_FILE_MOCK;
+ const mockPreprint = PREPRINT_MOCK;
+ const mockPreprintFile = FileModelMock.simple({
+ id: 'file-123',
+ name: 'example.pdf',
+ links: {
+ info: '',
+ move: 'move',
+ upload: '',
+ delete: '/v2/files/file-123/',
+ download: '',
+ render: '',
+ html: '',
+ self: '',
+ },
+ });
const mockCurrentFolder: FileFolderModel = OSF_FILE_MOCK;
const mockAvailableProjects = [
{ id: 'project-1', name: 'Test Project 1' },
@@ -63,31 +91,32 @@ describe('FileStepComponent', () => {
{ selector: PreprintStepperSelectors.isPreprintFilesLoading, value: false },
{ selector: PreprintStepperSelectors.getAvailableProjects, value: mockAvailableProjects },
{ selector: PreprintStepperSelectors.areAvailableProjectsLoading, value: false },
- { selector: PreprintStepperSelectors.getProjectFiles, value: mockProjectFiles },
+ { selector: PreprintStepperSelectors.getProjectFiles, value: [OSF_FILE_MOCK] },
{ selector: PreprintStepperSelectors.getFilesTotalCount, value: 1 },
{ selector: PreprintStepperSelectors.areProjectFilesLoading, value: false },
{ selector: PreprintStepperSelectors.getCurrentFolder, value: mockCurrentFolder },
{ selector: PreprintStepperSelectors.isCurrentFolderLoading, value: false },
];
- function setup(overrides?: {
- selectorOverrides?: SignalOverride[];
- provider?: PreprintProviderDetails;
- detectChanges?: boolean;
- }) {
- const signals = mergeSignalOverrides(defaultSignals, overrides?.selectorOverrides);
+ function setup(overrides: SetupOverrides = {}) {
toastServiceMock = ToastServiceMock.simple();
+ confirmationMock = CustomConfirmationServiceMock.simple();
TestBed.configureTestingModule({
imports: [FileStepComponent, ...MockComponents(IconComponent, FilesTreeComponent)],
- providers: [provideOSFCore(), MockProvider(ToastService, toastServiceMock), provideMockStore({ signals })],
+ providers: [
+ provideOSFCore(),
+ MockProvider(ToastService, toastServiceMock),
+ MockProvider(CustomConfirmationService, confirmationMock),
+ provideMockStore({ signals: mergeSignalOverrides(defaultSignals, overrides.selectorOverrides) }),
+ ],
});
store = TestBed.inject(Store);
fixture = TestBed.createComponent(FileStepComponent);
component = fixture.componentInstance;
- fixture.componentRef.setInput('provider', overrides?.provider ?? mockProvider);
- if (overrides?.detectChanges ?? true) {
+ fixture.componentRef.setInput('provider', overrides.provider ?? mockProvider);
+ if (overrides.detectChanges ?? true) {
fixture.detectChanges();
}
}
@@ -110,20 +139,32 @@ describe('FileStepComponent', () => {
vi.useRealTimers();
});
- it('should create', () => {
- setup();
- expect(component).toBeTruthy();
- });
-
- it('should compute state values', () => {
+ it('should create and expose default computed state', () => {
setup({ detectChanges: false });
+
+ expect(component).toBeTruthy();
+ expect(component.showDeleteButton()).toBe(false);
expect(component.preprintHasPrimaryFile()).toBe(true);
expect(component.isFileSourceSelected()).toBe(false);
- expect(component.canProceedToNext()).toBe(true);
expect(component.cancelSourceOptionButtonVisible()).toBe(false);
+
+ fixture.componentRef.setInput('showDeleteButton', true);
+ expect(component.showDeleteButton()).toBe(true);
});
- it('should dispatch fetch links and primary file fetch in ngOnInit', () => {
+ it('should show cancel source button when source selected and file missing', () => {
+ setup({
+ selectorOverrides: [
+ { selector: PreprintStepperSelectors.getPreprintFile, value: null },
+ { selector: PreprintStepperSelectors.getSelectedFileSource, value: PreprintFileSource.Computer },
+ ],
+ detectChanges: false,
+ });
+
+ expect(component.cancelSourceOptionButtonVisible()).toBe(true);
+ });
+
+ it('should dispatch fetch links and primary file when file id exists without loaded file', () => {
setup({
selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
detectChanges: false,
@@ -135,7 +176,7 @@ describe('FileStepComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
});
- it('should not dispatch primary file fetch in ngOnInit without primary file id', () => {
+ it('should dispatch fetch links only when primary file id is missing', () => {
setup({
selectorOverrides: [
{ selector: PreprintStepperSelectors.getPreprint, value: { ...mockPreprint, primaryFileId: null } },
@@ -149,7 +190,7 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
});
- it('should dispatch available projects from debounced projectNameControl value', () => {
+ it('should dispatch available projects from debounced project search', () => {
vi.useFakeTimers();
setup();
(store.dispatch as Mock).mockClear();
@@ -172,7 +213,7 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(new FetchAvailableProjects('project-1'));
});
- it('should handle selectFileSource for project and computer source', () => {
+ it('should select file source and fetch projects only for project source', () => {
setup({ detectChanges: false });
component.selectFileSource(PreprintFileSource.Project);
@@ -186,25 +227,19 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(new FetchAvailableProjects(null));
});
- it('should emit backClicked', () => {
+ it('should emit back and delete outputs', () => {
setup({ detectChanges: false });
- const emitSpy = vi.spyOn(component.backClicked, 'emit');
+ const backSpy = vi.spyOn(component.backClicked, 'emit');
+ const deleteSpy = vi.spyOn(component.deleteClicked, 'emit');
component.backButtonClicked();
-
- expect(emitSpy).toHaveBeenCalled();
- });
-
- it('should emit deleteClicked when deletePreprint is called', () => {
- setup({ detectChanges: false });
- const emitSpy = vi.spyOn(component.deleteClicked, 'emit');
-
component.deletePreprint();
- expect(emitSpy).toHaveBeenCalled();
+ expect(backSpy).toHaveBeenCalled();
+ expect(deleteSpy).toHaveBeenCalled();
});
- it('should handle nextButtonClicked for allowed and blocked states', () => {
+ it('should emit next and show success when file and primary file id exist', () => {
setup({ detectChanges: false });
const emitSpy = vi.spyOn(component.nextClicked, 'emit');
@@ -213,57 +248,45 @@ describe('FileStepComponent', () => {
expect(toastServiceMock.showSuccess).toHaveBeenCalledWith(
'preprints.preprintStepper.common.successMessages.preprintSaved'
);
- expect(emitSpy).toHaveBeenCalledTimes(1);
-
- component.versionFileMode.set(true);
- toastServiceMock.showSuccess.mockClear();
-
- component.nextButtonClicked();
-
- expect(toastServiceMock.showSuccess).not.toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledTimes(1);
+ expect(emitSpy).toHaveBeenCalled();
+ });
- vi.spyOn(component, 'preprint').mockReturnValue({ ...mockPreprint, primaryFileId: null } as PreprintModel);
- component.versionFileMode.set(false);
+ it('should block next when file requirements are missing', () => {
+ setup({
+ selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
+ detectChanges: false,
+ });
+ const emitSpy = vi.spyOn(component.nextClicked, 'emit');
component.nextButtonClicked();
expect(toastServiceMock.showSuccess).not.toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledTimes(1);
+ expect(emitSpy).not.toHaveBeenCalled();
});
- it('should skip file upload dispatches when no file is selected', () => {
+ it('should upload selected file and refetch primary file', () => {
setup({ detectChanges: false });
-
- const input = document.createElement('input');
- Object.defineProperty(input, 'files', { value: [] });
- component.onFileSelected({ target: input } as unknown as Event);
-
- expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(UploadFile));
- expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(ReuploadFile));
- });
-
- it('should handle upload and reupload flows in onFileSelected', () => {
- setup({ detectChanges: false });
- const file = new File(['file-body'], 'test.txt');
+ const file = new File(['content'], 'upload.pdf');
const input = document.createElement('input');
Object.defineProperty(input, 'files', { value: [file] });
component.onFileSelected({ target: input } as unknown as Event);
+
expect(store.dispatch).toHaveBeenCalledWith(new UploadFile(file));
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
+ });
- (store.dispatch as Mock).mockClear();
- component.versionFileMode.set(true);
+ it('should skip upload when no file is selected', () => {
+ setup({ detectChanges: false });
+ const input = document.createElement('input');
+ Object.defineProperty(input, 'files', { value: [] });
component.onFileSelected({ target: input } as unknown as Event);
- expect(store.dispatch).toHaveBeenCalledWith(new ReuploadFile(file));
- expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
- expect(component.versionFileMode()).toBe(false);
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(UploadFile));
});
- it('should handle selectProject with and without current folder files link', () => {
+ it('should select project and load files when folder link exists', () => {
setup({ detectChanges: false });
component.selectProject({
@@ -271,12 +294,16 @@ describe('FileStepComponent', () => {
originalEvent: new PointerEvent('click'),
} as SelectChangeEvent);
+ expect(component.selectedProjectId()).toBe('project-1');
expect(store.dispatch).toHaveBeenCalledWith(new SetProjectRootFolder('project-1'));
expect(store.dispatch).toHaveBeenCalledWith(new FetchProjectFilesByLink(mockCurrentFolder.links.filesLink, 1));
- expect(component.selectedProjectId()).toBe('project-1');
+ });
- vi.spyOn(component, 'currentFolder').mockReturnValue(null);
- (store.dispatch as Mock).mockClear();
+ it('should select project without loading files when folder link is missing', () => {
+ setup({
+ selectorOverrides: [{ selector: PreprintStepperSelectors.getCurrentFolder, value: null }],
+ detectChanges: false,
+ });
component.selectProject({
value: 'project-1',
@@ -287,7 +314,7 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchProjectFilesByLink));
});
- it('should return early in selectProject when original event is not pointer event', () => {
+ it('should ignore project selection when original event is not a pointer event', () => {
setup({ detectChanges: false });
component.selectProject({
@@ -298,9 +325,9 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(SetProjectRootFolder));
});
- it('should dispatch copy file from project and preprint file fetch', () => {
+ it('should copy project file and refetch primary file', () => {
setup({ detectChanges: false });
- const projectFile = OSF_FILE_MOCK as unknown as FileModel;
+ const projectFile = FileModelMock.simple({ id: 'project-file-1' });
component.selectProjectFile(projectFile);
@@ -308,29 +335,64 @@ describe('FileStepComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
});
- it('should handle cancelButtonClicked for file present and file missing states', () => {
+ it('should confirm delete preprint file and clear selected project on confirm', () => {
+ setup({ detectChanges: false });
+ component.selectedProjectId.set('project-1');
+
+ component.deletePreprintFile();
+
+ expect(confirmationMock.confirmDelete).toHaveBeenCalledWith({
+ headerKey: 'files.dialogs.deleteFile.title',
+ messageParams: { name: mockPreprintFile.name },
+ messageKey: 'files.dialogs.deleteFile.message',
+ onConfirm: expect.any(Function),
+ });
+
+ const { onConfirm } = confirmationMock.confirmDelete.mock.calls[0][0];
+ onConfirm();
+
+ expect(store.dispatch).toHaveBeenCalledWith(new DeletePreprintFile(mockPreprintFile.links.delete));
+ expect(component.selectedProjectId()).toBeNull();
+ expect(toastServiceMock.showSuccess).toHaveBeenCalledWith('files.dialogs.deleteFile.success');
+ });
+
+ it('should skip delete preprint file when file is missing', () => {
+ setup({
+ selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
+ detectChanges: false,
+ });
+
+ component.deletePreprintFile();
+
+ expect(confirmationMock.confirmDelete).not.toHaveBeenCalled();
+ });
+
+ it('should reset file source on cancel only when file is missing', () => {
setup({ detectChanges: false });
component.cancelButtonClicked();
expect(store.dispatch).not.toHaveBeenCalledWith(new SetSelectedPreprintFileSource(PreprintFileSource.None));
+ });
- vi.spyOn(component, 'preprintFile').mockReturnValue(null);
+ it('should reset file source on cancel when preprint file is absent', () => {
+ setup({
+ selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
+ detectChanges: false,
+ });
(store.dispatch as Mock).mockClear();
component.cancelButtonClicked();
+
expect(store.dispatch).toHaveBeenCalledWith(new SetSelectedPreprintFileSource(PreprintFileSource.None));
});
- it('should handle setCurrentFolder for unchanged and changed folders', () => {
+ it('should set current folder only when folder id changes', () => {
setup({ detectChanges: false });
component.setCurrentFolder(mockCurrentFolder);
expect(store.dispatch).not.toHaveBeenCalledWith(new SetPreprintStepperCurrentFolder(mockCurrentFolder));
- expect(store.dispatch).not.toHaveBeenCalledWith(new FetchProjectFilesByLink(mockCurrentFolder.links.filesLink, 1));
- (store.dispatch as Mock).mockClear();
const nextFolder = { ...mockCurrentFolder, id: 'folder-2' } as FileFolderModel;
-
component.setCurrentFolder(nextFolder);
expect(store.dispatch).toHaveBeenCalledWith(new SetPreprintStepperCurrentFolder(nextFolder));
@@ -344,19 +406,4 @@ describe('FileStepComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new FetchProjectFilesByLink('/v2/nodes/node-456/files/', 3));
});
-
- it('should default showDeleteButton to false', () => {
- setup();
-
- expect(component.showDeleteButton()).toBe(false);
- });
-
- it('should update showDeleteButton when input changes', () => {
- setup();
-
- fixture.componentRef.setInput('showDeleteButton', true);
- fixture.detectChanges();
-
- expect(component.showDeleteButton()).toBe(true);
- });
});
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 854f901b5..17e32f64b 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
@@ -25,16 +25,16 @@ import {
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
-import { PreprintFileSource } from '@osf/features/preprints/enums';
+import { PreprintFileSource, ReviewsState } from '@osf/features/preprints/enums';
import { PreprintProviderDetails } from '@osf/features/preprints/models';
import {
CopyFileFromProject,
+ DeletePreprintFile,
FetchAvailableProjects,
FetchPreprintFilesLinks,
FetchPreprintPrimaryFile,
FetchProjectFilesByLink,
PreprintStepperSelectors,
- ReuploadFile,
SetPreprintStepperCurrentFolder,
SetProjectRootFolder,
SetSelectedPreprintFileSource,
@@ -47,6 +47,7 @@ import { StringOrNull } from '@osf/shared/helpers/types.helper';
import { FileModel } from '@osf/shared/models/files/file.model';
import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
import { FilePageLinkModel } from '@osf/shared/models/files/file-page-link.model';
+import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
import { ToastService } from '@osf/shared/services/toast.service';
@Component({
@@ -70,14 +71,15 @@ import { ToastService } from '@osf/shared/services/toast.service';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileStepComponent implements OnInit {
- private toastService = inject(ToastService);
- private destroyRef = inject(DestroyRef);
+ private readonly toastService = inject(ToastService);
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly customConfirmationService = inject(CustomConfirmationService);
private actions = createDispatchMap({
setSelectedFileSource: SetSelectedPreprintFileSource,
getPreprintFilesLinks: FetchPreprintFilesLinks,
+ deletePreprintFile: DeletePreprintFile,
uploadFile: UploadFile,
- reuploadFile: ReuploadFile,
fetchPreprintFile: FetchPreprintPrimaryFile,
getAvailableProjects: FetchAvailableProjects,
setProjectRootFolder: SetProjectRootFolder,
@@ -88,42 +90,45 @@ export class FileStepComponent implements OnInit {
readonly PreprintFileSource = PreprintFileSource;
- provider = input.required();
- showDeleteButton = input(false);
- preprint = select(PreprintStepperSelectors.getPreprint);
- selectedFileSource = select(PreprintStepperSelectors.getSelectedFileSource);
- fileUploadLink = select(PreprintStepperSelectors.getUploadLink);
+ readonly provider = input.required();
+ readonly showDeleteButton = input(false);
- preprintFile = select(PreprintStepperSelectors.getPreprintFile);
- isPreprintFileLoading = select(PreprintStepperSelectors.isPreprintFilesLoading);
+ readonly nextClicked = output();
+ readonly backClicked = output();
+ readonly deleteClicked = output();
- availableProjects = select(PreprintStepperSelectors.getAvailableProjects);
- areAvailableProjectsLoading = select(PreprintStepperSelectors.areAvailableProjectsLoading);
+ readonly preprint = select(PreprintStepperSelectors.getPreprint);
+ readonly selectedFileSource = select(PreprintStepperSelectors.getSelectedFileSource);
+ readonly fileUploadLink = select(PreprintStepperSelectors.getUploadLink);
- projectFiles = select(PreprintStepperSelectors.getProjectFiles);
- filesTotalCount = select(PreprintStepperSelectors.getFilesTotalCount);
- areProjectFilesLoading = select(PreprintStepperSelectors.areProjectFilesLoading);
+ readonly preprintFile = select(PreprintStepperSelectors.getPreprintFile);
+ readonly isPreprintFileLoading = select(PreprintStepperSelectors.isPreprintFilesLoading);
- currentFolder = select(PreprintStepperSelectors.getCurrentFolder);
- isCurrentFolderLoading = select(PreprintStepperSelectors.isCurrentFolderLoading);
- selectedProjectId = signal(null);
+ readonly availableProjects = select(PreprintStepperSelectors.getAvailableProjects);
+ readonly areAvailableProjectsLoading = select(PreprintStepperSelectors.areAvailableProjectsLoading);
- versionFileMode = signal(false);
+ readonly projectFiles = select(PreprintStepperSelectors.getProjectFiles);
+ readonly filesTotalCount = select(PreprintStepperSelectors.getFilesTotalCount);
+ readonly areProjectFilesLoading = select(PreprintStepperSelectors.areProjectFilesLoading);
- preprintHasPrimaryFile = computed(() => !!this.preprint()?.primaryFileId);
+ readonly currentFolder = select(PreprintStepperSelectors.getCurrentFolder);
+ readonly isCurrentFolderLoading = select(PreprintStepperSelectors.isCurrentFolderLoading);
+ readonly selectedProjectId = signal(null);
- cancelSourceOptionButtonVisible = computed(
+ readonly preprintHasPrimaryFile = computed(() => !!this.preprint()?.primaryFileId);
+
+ readonly canDeletePrimaryFile = computed(() => {
+ const reviewsState = this.preprint()?.reviewsState;
+ return reviewsState === ReviewsState.Initial || reviewsState === ReviewsState.Rejected;
+ });
+
+ readonly cancelSourceOptionButtonVisible = computed(
() => !this.preprintFile() && this.selectedFileSource() !== PreprintFileSource.None && !this.isPreprintFileLoading()
);
- projectNameControl = new FormControl(null);
-
- nextClicked = output();
- backClicked = output();
- deleteClicked = output();
+ readonly projectNameControl = new FormControl(null);
- isFileSourceSelected = computed(() => this.selectedFileSource() !== PreprintFileSource.None);
- canProceedToNext = computed(() => !!this.preprintFile() && !this.versionFileMode());
+ readonly isFileSourceSelected = computed(() => this.selectedFileSource() !== PreprintFileSource.None);
ngOnInit() {
this.actions.getPreprintFilesLinks();
@@ -156,7 +161,7 @@ export class FileStepComponent implements OnInit {
}
nextButtonClicked() {
- if (!this.canProceedToNext() || !this.preprint()?.primaryFileId) {
+ if (!this.preprintFile() || !this.preprint()?.primaryFileId) {
return;
}
@@ -173,14 +178,10 @@ export class FileStepComponent implements OnInit {
const file = input.files?.[0];
if (!file) return;
- const isVersionFileMode = this.versionFileMode();
-
- if (isVersionFileMode) {
- this.versionFileMode.set(false);
- }
-
- const uploadAction = isVersionFileMode ? this.actions.reuploadFile(file) : this.actions.uploadFile(file);
- uploadAction.subscribe(() => this.actions.fetchPreprintFile());
+ this.actions
+ .uploadFile(file)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => this.actions.fetchPreprintFile());
}
selectProject(event: SelectChangeEvent) {
@@ -200,13 +201,40 @@ export class FileStepComponent implements OnInit {
} else {
return EMPTY;
}
- })
+ }),
+ takeUntilDestroyed(this.destroyRef)
)
.subscribe();
}
selectProjectFile(file: FileModel) {
- this.actions.copyFileFromProject(file).subscribe(() => this.actions.fetchPreprintFile());
+ this.actions
+ .copyFileFromProject(file)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => this.actions.fetchPreprintFile());
+ }
+
+ deletePreprintFile() {
+ const file = this.preprintFile();
+
+ if (!file || !this.canDeletePrimaryFile()) {
+ return;
+ }
+
+ this.customConfirmationService.confirmDelete({
+ headerKey: 'files.dialogs.deleteFile.title',
+ messageParams: { name: file.name },
+ messageKey: 'files.dialogs.deleteFile.message',
+ onConfirm: () => {
+ this.actions
+ .deletePreprintFile(file.links.delete)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(() => {
+ this.selectedProjectId.set(null);
+ this.toastService.showSuccess('files.dialogs.deleteFile.success');
+ });
+ },
+ });
}
cancelButtonClicked() {
diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
index 4466c5155..7c22b9397 100644
--- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
+++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
@@ -53,10 +53,10 @@ export class UpdatePrimaryFileRelationship {
constructor(public fileId: string) {}
}
-export class ReuploadFile {
- static readonly type = '[Preprint Stepper] Reupload File';
+export class DeletePreprintFile {
+ static readonly type = '[Preprint Stepper] Delete Preprint File';
- constructor(public file: File) {}
+ constructor(public deleteLink: string) {}
}
export class CopyFileFromProject {
diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
index 7606d7947..3311f6e14 100644
--- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
+++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
@@ -14,6 +14,7 @@ import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
import { FilesService } from '@osf/shared/services/files.service';
import { LicensesService } from '@osf/shared/services/licenses.service';
+import { PreprintFileSource } from '../../enums';
import { PreprintFilesService } from '../../services/preprint-files.service';
import { PreprintLicensesService } from '../../services/preprint-licenses.service';
import { PreprintsService } from '../../services/preprints.service';
@@ -25,6 +26,7 @@ import {
CreateNewProject,
CreateNewVersion,
CreatePreprint,
+ DeletePreprintFile,
DisconnectProject,
FetchAvailableProjects,
FetchLicenses,
@@ -34,7 +36,6 @@ import {
FetchPreprintProject,
FetchProjectFilesByLink,
ResetPreprintStepperState,
- ReuploadFile,
SaveLicense,
SetInstitutionsChanged,
SetPreprintStepperCurrentFolder,
@@ -157,20 +158,31 @@ export class PreprintStepperState {
);
}
- @Action(ReuploadFile)
- reuploadFile(ctx: StateContext, action: ReuploadFile) {
- const state = ctx.getState();
- const uploadedFile = state.preprintFile.data;
- if (!uploadedFile) return EMPTY;
-
+ @Action(DeletePreprintFile)
+ deletePreprintFile(ctx: StateContext, action: DeletePreprintFile) {
ctx.setState(patch({ preprintFile: patch({ isLoading: true }) }));
- return this.fileService.updateFileContent(action.file, uploadedFile.links.upload).pipe(
- switchMap(() =>
- uploadedFile.name !== action.file.name
- ? this.fileService.renameEntry(uploadedFile.links.upload, action.file.name, 'replace')
- : of(null)
- ),
+ return this.fileService.deleteEntry(action.deleteLink).pipe(
+ tap(() => {
+ const state = ctx.getState();
+ ctx.patchState({
+ preprintFile: {
+ data: null,
+ isLoading: false,
+ error: null,
+ },
+ fileSource: PreprintFileSource.None,
+ preprint: {
+ ...state.preprint,
+ data: state.preprint.data
+ ? {
+ ...state.preprint.data,
+ primaryFileId: null,
+ }
+ : null,
+ },
+ });
+ }),
catchError((error) => handleSectionError(ctx, 'preprintFile', error))
);
}
From 3da44051998dd99fbecf3f5f11b9f429ac60a63f Mon Sep 17 00:00:00 2001
From: nsemets
Date: Mon, 9 Nov 2026 12:40:37 +0200
Subject: [PATCH 2/3] fix(file-step): removed text and fixed some issues
---
.../file-step/file-step.component.html | 15 +--
.../file-step/file-step.component.spec.ts | 100 ++++++++++++++----
.../stepper/file-step/file-step.component.ts | 45 +++++---
src/assets/i18n/en.json | 5 -
4 files changed, 117 insertions(+), 48 deletions(-)
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 4d4c4f14c..a2da41c20 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
@@ -7,10 +7,9 @@ {{ 'preprints.preprintStepper.file.title' | translate }}
| translate: { preprintWord: provider().preprintWord | titlecase }
}}
- {{ 'preprints.preprintStepper.file.note' | translate }}
-@if (!preprintHasPrimaryFile()) {
+@if (preprint() && !preprintHasPrimaryFile()) {
{{ 'preprints.preprintStepper.file.title' | translate }}
[label]="'preprints.preprintStepper.file.uploadFromComputer' | translate"
severity="secondary"
[disabled]="isFileSourceSelected()"
- [pTooltip]="
- isFileSourceSelected() ? ('preprints.preprintStepper.file.tooltips.computerDisabled' | translate) : ''
- "
- tooltipPosition="top"
(onClick)="selectFileSource(PreprintFileSource.Computer)"
/>
{{ 'preprints.preprintStepper.file.title' | translate }}
[label]="'preprints.preprintStepper.file.selectFromProject' | translate"
severity="secondary"
[disabled]="isFileSourceSelected()"
- [pTooltip]="isFileSourceSelected() ? ('preprints.preprintStepper.file.tooltips.projectDisabled' | translate) : ''"
- tooltipPosition="top"
(onClick)="selectFileSource(PreprintFileSource.Project)"
/>
}
-@if (selectedFileSource() === PreprintFileSource.Computer) {
+@if (selectedFileSource() === PreprintFileSource.Computer && !preprintFile() && !isPreprintFileLoading()) {
@if (!fileUploadLink()) {
- } @else if (!preprintFile() && !isPreprintFileLoading()) {
+ } @else {
{{ 'preprints.preprintStepper.file.title' | translate }}
severity="danger"
text
[ariaLabel]="'common.buttons.delete' | translate"
+ [pTooltip]="'common.buttons.delete' | translate"
+ tooltipPosition="top"
(onClick)="deletePreprintFile()"
/>
}
diff --git a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
index 9f54a2fc3..33580c85e 100644
--- a/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
+++ b/src/app/features/preprints/components/stepper/file-step/file-step.component.spec.ts
@@ -8,7 +8,7 @@ import { Mock } from 'vitest';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { PreprintFileSource } from '@osf/features/preprints/enums';
+import { PreprintFileSource, ReviewsState } from '@osf/features/preprints/enums';
import { PreprintProviderDetails } from '@osf/features/preprints/models';
import {
CopyFileFromProject,
@@ -62,7 +62,7 @@ describe('FileStepComponent', () => {
const originalPointerEvent = (globalThis as unknown as { PointerEvent?: typeof Event }).PointerEvent;
const mockProvider: PreprintProviderDetails = PREPRINT_PROVIDER_DETAILS_MOCK;
- const mockPreprint = PREPRINT_MOCK;
+ const mockPreprint = { ...PREPRINT_MOCK, reviewsState: ReviewsState.Initial };
const mockPreprintFile = FileModelMock.simple({
id: 'file-123',
name: 'example.pdf',
@@ -145,6 +145,7 @@ describe('FileStepComponent', () => {
expect(component).toBeTruthy();
expect(component.showDeleteButton()).toBe(false);
expect(component.preprintHasPrimaryFile()).toBe(true);
+ expect(component.canDeletePrimaryFile()).toBe(true);
expect(component.isFileSourceSelected()).toBe(false);
expect(component.cancelSourceOptionButtonVisible()).toBe(false);
@@ -152,6 +153,34 @@ describe('FileStepComponent', () => {
expect(component.showDeleteButton()).toBe(true);
});
+ it('should disallow delete for non-initial and non-rejected review states', () => {
+ setup({
+ selectorOverrides: [
+ {
+ selector: PreprintStepperSelectors.getPreprint,
+ value: { ...mockPreprint, reviewsState: ReviewsState.Pending },
+ },
+ ],
+ detectChanges: false,
+ });
+
+ expect(component.canDeletePrimaryFile()).toBe(false);
+ });
+
+ it('should allow delete for rejected review state', () => {
+ setup({
+ selectorOverrides: [
+ {
+ selector: PreprintStepperSelectors.getPreprint,
+ value: { ...mockPreprint, reviewsState: ReviewsState.Rejected },
+ },
+ ],
+ detectChanges: false,
+ });
+
+ expect(component.canDeletePrimaryFile()).toBe(true);
+ });
+
it('should show cancel source button when source selected and file missing', () => {
setup({
selectorOverrides: [
@@ -164,14 +193,18 @@ describe('FileStepComponent', () => {
expect(component.cancelSourceOptionButtonVisible()).toBe(true);
});
- it('should dispatch fetch links and primary file when file id exists without loaded file', () => {
+ it('should dispatch fetch links and skip primary file when file is already loaded', () => {
+ setup();
+
+ expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintFilesLinks());
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
+ });
+
+ it('should dispatch fetch links and primary file when primary file id exists without loaded file', () => {
setup({
selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
- detectChanges: false,
});
- component.ngOnInit();
-
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintFilesLinks());
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
});
@@ -181,15 +214,21 @@ describe('FileStepComponent', () => {
selectorOverrides: [
{ selector: PreprintStepperSelectors.getPreprint, value: { ...mockPreprint, primaryFileId: null } },
],
- detectChanges: false,
});
- component.ngOnInit();
-
expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintFilesLinks());
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
});
+ it('should skip init fetches when preprint is missing', () => {
+ setup({
+ selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprint, value: null }],
+ });
+
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintFilesLinks));
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
+ });
+
it('should dispatch available projects from debounced project search', () => {
vi.useFakeTimers();
setup();
@@ -264,7 +303,7 @@ describe('FileStepComponent', () => {
expect(emitSpy).not.toHaveBeenCalled();
});
- it('should upload selected file and refetch primary file', () => {
+ it('should upload selected file', () => {
setup({ detectChanges: false });
const file = new File(['content'], 'upload.pdf');
const input = document.createElement('input');
@@ -273,7 +312,7 @@ describe('FileStepComponent', () => {
component.onFileSelected({ target: input } as unknown as Event);
expect(store.dispatch).toHaveBeenCalledWith(new UploadFile(file));
- expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
});
it('should skip upload when no file is selected', () => {
@@ -325,19 +364,20 @@ describe('FileStepComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(SetProjectRootFolder));
});
- it('should copy project file and refetch primary file', () => {
+ it('should copy project file', () => {
setup({ detectChanges: false });
const projectFile = FileModelMock.simple({ id: 'project-file-1' });
component.selectProjectFile(projectFile);
expect(store.dispatch).toHaveBeenCalledWith(new CopyFileFromProject(projectFile));
- expect(store.dispatch).toHaveBeenCalledWith(new FetchPreprintPrimaryFile());
+ expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(FetchPreprintPrimaryFile));
});
- it('should confirm delete preprint file and clear selected project on confirm', () => {
+ it('should confirm delete preprint file and clear project selection on confirm', () => {
setup({ detectChanges: false });
component.selectedProjectId.set('project-1');
+ component.projectNameControl.setValue('project-1', { emitEvent: false });
component.deletePreprintFile();
@@ -353,6 +393,7 @@ describe('FileStepComponent', () => {
expect(store.dispatch).toHaveBeenCalledWith(new DeletePreprintFile(mockPreprintFile.links.delete));
expect(component.selectedProjectId()).toBeNull();
+ expect(component.projectNameControl.value).toBeNull();
expect(toastServiceMock.showSuccess).toHaveBeenCalledWith('files.dialogs.deleteFile.success');
});
@@ -367,25 +408,46 @@ describe('FileStepComponent', () => {
expect(confirmationMock.confirmDelete).not.toHaveBeenCalled();
});
- it('should reset file source on cancel only when file is missing', () => {
- setup({ detectChanges: false });
+ it('should skip delete preprint file when review state does not allow delete', () => {
+ setup({
+ selectorOverrides: [
+ {
+ selector: PreprintStepperSelectors.getPreprint,
+ value: { ...mockPreprint, reviewsState: ReviewsState.Pending },
+ },
+ ],
+ detectChanges: false,
+ });
- component.cancelButtonClicked();
- expect(store.dispatch).not.toHaveBeenCalledWith(new SetSelectedPreprintFileSource(PreprintFileSource.None));
+ component.deletePreprintFile();
+
+ expect(confirmationMock.confirmDelete).not.toHaveBeenCalled();
});
- it('should reset file source on cancel when preprint file is absent', () => {
+ it('should reset file source and project selection on cancel when file is missing', () => {
setup({
selectorOverrides: [{ selector: PreprintStepperSelectors.getPreprintFile, value: null }],
detectChanges: false,
});
+ component.selectedProjectId.set('project-1');
+ component.projectNameControl.setValue('project-1', { emitEvent: false });
(store.dispatch as Mock).mockClear();
component.cancelButtonClicked();
+ expect(component.selectedProjectId()).toBeNull();
+ expect(component.projectNameControl.value).toBeNull();
expect(store.dispatch).toHaveBeenCalledWith(new SetSelectedPreprintFileSource(PreprintFileSource.None));
});
+ it('should keep file source on cancel when file is present', () => {
+ setup({ detectChanges: false });
+
+ component.cancelButtonClicked();
+
+ expect(store.dispatch).not.toHaveBeenCalledWith(new SetSelectedPreprintFileSource(PreprintFileSource.None));
+ });
+
it('should set current folder only when folder id changes', () => {
setup({ detectChanges: false });
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 17e32f64b..48ac4a585 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
@@ -16,11 +16,13 @@ import {
Component,
computed,
DestroyRef,
+ effect,
inject,
input,
OnInit,
output,
signal,
+ untracked,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@@ -115,7 +117,9 @@ export class FileStepComponent implements OnInit {
readonly isCurrentFolderLoading = select(PreprintStepperSelectors.isCurrentFolderLoading);
readonly selectedProjectId = signal(null);
- readonly preprintHasPrimaryFile = computed(() => !!this.preprint()?.primaryFileId);
+ readonly preprintId = computed(() => this.preprint()?.id ?? null);
+ readonly primaryFileId = computed(() => this.preprint()?.primaryFileId ?? null);
+ readonly preprintHasPrimaryFile = computed(() => !!this.primaryFileId());
readonly canDeletePrimaryFile = computed(() => {
const reviewsState = this.preprint()?.reviewsState;
@@ -130,13 +134,29 @@ export class FileStepComponent implements OnInit {
readonly isFileSourceSelected = computed(() => this.selectedFileSource() !== PreprintFileSource.None);
- ngOnInit() {
- this.actions.getPreprintFilesLinks();
+ constructor() {
+ effect(() => {
+ if (!this.preprintId()) {
+ return;
+ }
+
+ this.actions.getPreprintFilesLinks();
+ });
+
+ effect(() => {
+ if (!this.primaryFileId()) {
+ return;
+ }
+
+ if (untracked(() => this.preprintFile())) {
+ return;
+ }
- if (this.preprintHasPrimaryFile() && !this.preprintFile()) {
this.actions.fetchPreprintFile();
- }
+ });
+ }
+ ngOnInit() {
this.projectNameControl.valueChanges
.pipe(debounceTime(500), distinctUntilChanged(), takeUntilDestroyed(this.destroyRef))
.subscribe((projectNameOrId) => {
@@ -161,7 +181,7 @@ export class FileStepComponent implements OnInit {
}
nextButtonClicked() {
- if (!this.preprintFile() || !this.preprint()?.primaryFileId) {
+ if (!this.preprintFile() || !this.primaryFileId()) {
return;
}
@@ -178,10 +198,7 @@ export class FileStepComponent implements OnInit {
const file = input.files?.[0];
if (!file) return;
- this.actions
- .uploadFile(file)
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe(() => this.actions.fetchPreprintFile());
+ this.actions.uploadFile(file).pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
}
selectProject(event: SelectChangeEvent) {
@@ -208,10 +225,7 @@ export class FileStepComponent implements OnInit {
}
selectProjectFile(file: FileModel) {
- this.actions
- .copyFileFromProject(file)
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe(() => this.actions.fetchPreprintFile());
+ this.actions.copyFileFromProject(file).pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
}
deletePreprintFile() {
@@ -231,6 +245,7 @@ export class FileStepComponent implements OnInit {
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
this.selectedProjectId.set(null);
+ this.projectNameControl.setValue(null, { emitEvent: false });
this.toastService.showSuccess('files.dialogs.deleteFile.success');
});
},
@@ -242,6 +257,8 @@ export class FileStepComponent implements OnInit {
return;
}
+ this.selectedProjectId.set(null);
+ this.projectNameControl.setValue(null, { emitEvent: false });
this.actions.setSelectedFileSource(PreprintFileSource.None);
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 7304da4ff..d4e7e7ad4 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1541,13 +1541,8 @@
"success": "Preprint draft successfully deleted."
},
"file": {
- "note": "Note: You cannot switch options once a file is attached.",
"selectFromProject": "Select From Existing OSF Project",
"title": "File",
- "tooltips": {
- "computerDisabled": "Start a new preprint to attach a file from your computer.",
- "projectDisabled": "Start a new preprint to attach a file from your project."
- },
"uploadDescription": "Please Upload your {{preprintWord}}",
"uploadFileButton": "Upload file",
"uploadFromComputer": "Upload From Your Computer"
From ee59acb1b236513058b8c40e70c85ccbc3729607 Mon Sep 17 00:00:00 2001
From: nsemets
Date: Mon, 9 Nov 2026 15:13:45 +0200
Subject: [PATCH 3/3] fix(state): fixed error handling
---
.../preprint-stepper/preprint-stepper.actions.ts | 2 +-
.../preprint-stepper/preprint-stepper.state.ts | 14 ++++++++++----
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
index 7c22b9397..01baff0b7 100644
--- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
+++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.actions.ts
@@ -132,7 +132,7 @@ export class CreateNewProject {
}
export class SubmitPreprint {
- static readonly type = '[Preprint Stepper] Preprint Stepper';
+ static readonly type = '[Preprint Stepper] Submit Preprint';
}
export class CreateNewVersion {
diff --git a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
index 3311f6e14..f30440c62 100644
--- a/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
+++ b/src/app/features/preprints/store/preprint-stepper/preprint-stepper.state.ts
@@ -111,7 +111,8 @@ export class PreprintStepperState {
return this.preprintFilesService.getPreprintFilesLinks(state.preprint.data.id).pipe(
tap((preprintStorage) => {
ctx.setState(patch({ preprintFilesLinks: patch({ isLoading: false, data: preprintStorage }) }));
- })
+ }),
+ catchError((error) => handleSectionError(ctx, 'preprintFilesLinks', error))
);
}
@@ -131,7 +132,8 @@ export class PreprintStepperState {
const createdFileId = file.id.split('/')[1];
return ctx.dispatch(new UpdatePrimaryFileRelationship(createdFileId));
- })
+ }),
+ catchError((error) => handleSectionError(ctx, 'preprintFile', error))
);
}
@@ -299,7 +301,7 @@ export class PreprintStepperState {
copyFileFromProject(ctx: StateContext, action: CopyFileFromProject) {
const createdPreprintId = ctx.getState().preprint.data?.id;
if (!createdPreprintId) {
- return;
+ return EMPTY;
}
ctx.setState(patch({ preprintFile: patch({ isLoading: true }) }));
@@ -469,7 +471,11 @@ export class PreprintStepperState {
@Action(SubmitPreprint)
submitPreprint(ctx: StateContext) {
- const createdPreprintId = ctx.getState().preprint.data!.id;
+ const createdPreprintId = ctx.getState().preprint.data?.id;
+ if (!createdPreprintId) {
+ return EMPTY;
+ }
+
ctx.setState(patch({ preprint: patch({ isSubmitting: true }) }));
return this.preprintsService.submitPreprint(createdPreprintId).pipe(