diff --git a/src/app/features/files/pages/files/files.component.html b/src/app/features/files/pages/files/files.component.html
index 43d076f6b..10a2f47bb 100644
--- a/src/app/features/files/pages/files/files.component.html
+++ b/src/app/features/files/pages/files/files.component.html
@@ -144,8 +144,8 @@
(selectFile)="onFileTreeSelected($event)"
(unselectFile)="onFileTreeUnselected($event)"
(clearSelection)="onClearSelection()"
- (entryFileClicked)="navigateToFile($event)"
(deleteEntryAction)="deleteEntry($event)"
+ (entryFileClicked)="navigateToFile($event)"
(renameEntryAction)="renameEntry($event)"
(uploadFilesConfirmed)="uploadFiles($event)"
(loadFiles)="onLoadFiles($event)"
diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts
index a264e749a..00ab2a2ef 100644
--- a/src/app/features/files/pages/files/files.component.ts
+++ b/src/app/features/files/pages/files/files.component.ts
@@ -619,8 +619,8 @@ export class FilesComponent {
this.actions.setMoveDialogCurrentFolder(folder);
}
- deleteEntry(link: string) {
- this.actions.deleteEntry(link).subscribe(() => {
+ deleteEntry(file: FileModel): void {
+ this.actions.deleteEntry(file?.links.delete).subscribe(() => {
this.toastService.showSuccess('files.dialogs.deleteFile.success');
this.updateFilesList();
});
diff --git a/src/app/features/registries/components/custom-step/custom-step.component.html b/src/app/features/registries/components/custom-step/custom-step.component.html
index 6bdc77c6e..e3d0540a6 100644
--- a/src/app/features/registries/components/custom-step/custom-step.component.html
+++ b/src/app/features/registries/components/custom-step/custom-step.component.html
@@ -168,7 +168,7 @@
{{ 'files.actions.uploadFile' | translate }}
[label]="file.name"
severity="info"
removable="true"
- (onRemove)="removeFromAttachedFiles(file, q.responseKey!)"
+ (onRemove)="removeFromAttachedFiles(file.file_id, q.responseKey!)"
/>
}
@@ -181,6 +181,7 @@ {{ 'files.actions.uploadFile' | translate }}
(attachFile)="onAttachFile($event, q.responseKey!)"
(openFile)="onOpenFile($event)"
[filesViewOnly]="filesViewOnly()"
+ (removeFromAttachedFiles)="removeFromAttachedFiles($event, q.responseKey!)"
>
}
diff --git a/src/app/features/registries/components/custom-step/custom-step.component.spec.ts b/src/app/features/registries/components/custom-step/custom-step.component.spec.ts
index b17f69381..68cca32a7 100644
--- a/src/app/features/registries/components/custom-step/custom-step.component.spec.ts
+++ b/src/app/features/registries/components/custom-step/custom-step.component.spec.ts
@@ -213,7 +213,7 @@ describe('CustomStepComponent', () => {
{ file_id: 'f2', name: 'b' },
];
- component.removeFromAttachedFiles({ file_id: 'f1', name: 'a' }, 'field1');
+ component.removeFromAttachedFiles('f1', 'field1');
expect(component.attachedFiles['field1'].length).toBe(1);
expect(component.attachedFiles['field1'][0].file_id).toBe('f2');
@@ -223,7 +223,7 @@ describe('CustomStepComponent', () => {
it('should skip non-existent questionKey', () => {
const { component } = setup();
const emitSpy = vi.spyOn(component.updateAction, 'emit');
- component.removeFromAttachedFiles({ file_id: 'f1' }, 'nonexistent');
+ component.removeFromAttachedFiles('f1', 'nonexistent');
expect(emitSpy).not.toHaveBeenCalled();
});
diff --git a/src/app/features/registries/components/custom-step/custom-step.component.ts b/src/app/features/registries/components/custom-step/custom-step.component.ts
index 7b5f066e6..484b5e4a9 100644
--- a/src/app/features/registries/components/custom-step/custom-step.component.ts
+++ b/src/app/features/registries/components/custom-step/custom-step.component.ts
@@ -143,12 +143,12 @@ export class CustomStepComponent implements OnDestroy {
}
}
- removeFromAttachedFiles(file: AttachedFile, questionKey: string): void {
+ removeFromAttachedFiles(fileId: string | undefined, questionKey: string): void {
if (!this.attachedFiles[questionKey]) {
return;
}
- this.attachedFiles[questionKey] = this.attachedFiles[questionKey].filter((f) => f.file_id !== file.file_id);
+ this.attachedFiles[questionKey] = this.attachedFiles[questionKey].filter((f) => f.file_id !== fileId);
this.stepForm.patchValue({ [questionKey]: this.attachedFiles[questionKey] });
this.updateAction.emit({
[questionKey]: this.mapFilesToPayload(this.attachedFiles[questionKey]),
diff --git a/src/app/features/registries/components/files-control/files-control.component.html b/src/app/features/registries/components/files-control/files-control.component.html
index 78c18d21c..951fcac20 100644
--- a/src/app/features/registries/components/files-control/files-control.component.html
+++ b/src/app/features/registries/components/files-control/files-control.component.html
@@ -51,6 +51,8 @@
[resourceId]="projectId()"
[provider]="provider()"
[selectedFiles]="filesSelection"
+ [isDraftResource]="true"
+ (deleteEntryAction)="deleteEntry($event)"
(selectFile)="onFileTreeSelected($event)"
(entryFileClicked)="onEntryFileClicked($event)"
(uploadFilesConfirmed)="uploadFiles($event)"
diff --git a/src/app/features/registries/components/files-control/files-control.component.spec.ts b/src/app/features/registries/components/files-control/files-control.component.spec.ts
index 874ea26d7..1557f575a 100644
--- a/src/app/features/registries/components/files-control/files-control.component.spec.ts
+++ b/src/app/features/registries/components/files-control/files-control.component.spec.ts
@@ -228,4 +228,19 @@ describe('FilesControlComponent', () => {
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(SetFilesIsLoading));
expect(store.dispatch).not.toHaveBeenCalledWith(expect.any(GetFiles));
});
+
+ it('should delete entry, show success toast, refresh files, and emit removal', () => {
+ const file = { id: 'file-1', links: { delete: '/delete-link' } } as FileModel;
+ const deleteSpy = vi.spyOn(component['actions'], 'deleteDraftRegistrationFiles').mockReturnValue(of(void 0));
+ const refreshSpy = vi.spyOn(component as any, 'refreshFilesList');
+ const emitSpy = vi.spyOn(component.removeFromAttachedFiles, 'emit');
+ const toastSpy = vi.spyOn(toastService, 'showSuccess');
+
+ component.deleteEntry(file);
+
+ expect(deleteSpy).toHaveBeenCalledWith('/delete-link');
+ expect(toastSpy).toHaveBeenCalledWith('files.dialogs.deleteFile.success');
+ expect(refreshSpy).toHaveBeenCalled();
+ expect(emitSpy).toHaveBeenCalledWith('file-1');
+ });
});
diff --git a/src/app/features/registries/components/files-control/files-control.component.ts b/src/app/features/registries/components/files-control/files-control.component.ts
index 604141db5..fd367abbc 100644
--- a/src/app/features/registries/components/files-control/files-control.component.ts
+++ b/src/app/features/registries/components/files-control/files-control.component.ts
@@ -25,6 +25,7 @@ import { FileFolderModel } from '@shared/models/files/file-folder.model';
import {
CreateFolder,
+ DeleteDraftRegistrationFiles,
GetFiles,
GetRootFolders,
RegistriesSelectors,
@@ -54,6 +55,7 @@ export class FilesControlComponent {
provider = input.required();
filesViewOnly = input(false);
attachFile = output();
+ removeFromAttachedFiles = output();
openFile = output();
private readonly filesService = inject(FilesService);
@@ -79,6 +81,7 @@ export class FilesControlComponent {
setFilesIsLoading: SetFilesIsLoading,
setCurrentFolder: SetRegistriesCurrentFolder,
getRootFolders: GetRootFolders,
+ deleteDraftRegistrationFiles: DeleteDraftRegistrationFiles,
});
constructor() {
@@ -86,6 +89,14 @@ export class FilesControlComponent {
this.setupCurrentFolderWatcher();
}
+ deleteEntry(file: FileModel): void {
+ this.actions.deleteDraftRegistrationFiles(file?.links.delete).subscribe(() => {
+ this.toastService.showSuccess('files.dialogs.deleteFile.success');
+ this.refreshFilesList();
+ this.removeFromAttachedFiles.emit(file.id);
+ });
+ }
+
onFileSelected(event: Event): void {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
diff --git a/src/app/features/registries/store/handlers/files.handlers.ts b/src/app/features/registries/store/handlers/files.handlers.ts
index e6b5d4f38..615386a72 100644
--- a/src/app/features/registries/store/handlers/files.handlers.ts
+++ b/src/app/features/registries/store/handlers/files.handlers.ts
@@ -7,7 +7,7 @@ import { inject, Injectable } from '@angular/core';
import { handleSectionError } from '@osf/shared/helpers/state-error.handler';
import { FilesService } from '@osf/shared/services/files.service';
-import { CreateFolder, GetFiles, GetRootFolders } from '../registries.actions';
+import { CreateFolder, DeleteDraftRegistrationFiles, GetFiles, GetRootFolders } from '../registries.actions';
import { RegistriesStateModel } from '../registries.model';
@Injectable()
@@ -70,4 +70,13 @@ export class FilesHandlers {
.createFolder(action.newFolderLink, action.folderName)
.pipe(finalize(() => ctx.patchState({ files: { ...state.files, isLoading: false, error: null } })));
}
+
+ deleteDraftRegistrationFiles(ctx: StateContext, action: DeleteDraftRegistrationFiles) {
+ const state = ctx.getState();
+ ctx.patchState({ files: { ...state.files, isLoading: true, error: null } });
+
+ return this.filesService
+ .deleteEntry(action.link)
+ .pipe(finalize(() => ctx.patchState({ files: { ...state.files, isLoading: false, error: null } })));
+ }
}
diff --git a/src/app/features/registries/store/registries.actions.ts b/src/app/features/registries/store/registries.actions.ts
index 45db0f8f9..25a553773 100644
--- a/src/app/features/registries/store/registries.actions.ts
+++ b/src/app/features/registries/store/registries.actions.ts
@@ -135,6 +135,12 @@ export class GetFiles {
) {}
}
+export class DeleteDraftRegistrationFiles {
+ static readonly type = '[Registries] Delete Draft Registration Files';
+
+ constructor(public link: string) {}
+}
+
export class SetFilesIsLoading {
static readonly type = '[Registries] Set Files Loading';
diff --git a/src/app/features/registries/store/registries.state.ts b/src/app/features/registries/store/registries.state.ts
index d24602bbf..c2373159c 100644
--- a/src/app/features/registries/store/registries.state.ts
+++ b/src/app/features/registries/store/registries.state.ts
@@ -23,6 +23,7 @@ import {
CreateFolder,
CreateSchemaResponse,
DeleteDraft,
+ DeleteDraftRegistrationFiles,
DeleteSchemaResponse,
FetchAllSchemaResponses,
FetchDraft,
@@ -351,6 +352,11 @@ export class RegistriesState {
return this.filesHandlers.getProjectFiles(ctx, { filesLink, page });
}
+ @Action(DeleteDraftRegistrationFiles)
+ deleteDraftRegistrationFiles(ctx: StateContext, action: DeleteDraftRegistrationFiles) {
+ return this.filesHandlers.deleteDraftRegistrationFiles(ctx, action);
+ }
+
@Action(GetRootFolders)
getRootFolders(ctx: StateContext, action: GetRootFolders) {
return this.filesHandlers.getRootFolders(ctx, action);
diff --git a/src/app/shared/components/files-tree/files-tree.component.html b/src/app/shared/components/files-tree/files-tree.component.html
index fa2954b12..ba61a2c22 100644
--- a/src/app/shared/components/files-tree/files-tree.component.html
+++ b/src/app/shared/components/files-tree/files-tree.component.html
@@ -95,6 +95,18 @@
}
+ @if (isDraftResource()) {
+
+ }
}
diff --git a/src/app/shared/components/files-tree/files-tree.component.ts b/src/app/shared/components/files-tree/files-tree.component.ts
index f08df941e..efe638512 100644
--- a/src/app/shared/components/files-tree/files-tree.component.ts
+++ b/src/app/shared/components/files-tree/files-tree.component.ts
@@ -3,6 +3,8 @@ import { select } from '@ngxs/store';
import { TranslatePipe } from '@ngx-translate/core';
import { PrimeTemplate, TreeNode } from 'primeng/api';
+import { Button } from 'primeng/button';
+import { Tooltip } from 'primeng/tooltip';
import { Tree, TreeLazyLoadEvent, TreeNodeDropEvent, TreeNodeSelectEvent } from 'primeng/tree';
import { Clipboard } from '@angular/cdk/clipboard';
@@ -66,6 +68,8 @@ type FileTreeNode = FileModel & TreeNode;
LoadingSpinnerComponent,
FileMenuComponent,
StopPropagationDirective,
+ Button,
+ Tooltip,
],
templateUrl: './files-tree.component.html',
styleUrl: './files-tree.component.scss',
@@ -101,12 +105,13 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit {
selectedFiles = input([]);
scrollHeight = input('300px');
selectionMode = input<'multiple' | null>('multiple');
+ isDraftResource = input(false);
entryFileClicked = output();
uploadFilesConfirmed = output();
setCurrentFolder = output();
setMoveDialogCurrentFolder = output();
- deleteEntryAction = output();
+ deleteEntryAction = output();
renameEntryAction = output<{ newName: string; link: string }>();
loadFiles = output<{ link: string; page: number }>();
selectFile = output();
@@ -344,12 +349,12 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit {
messageKey:
file.kind === FileKind.Folder ? 'files.dialogs.deleteFolder.message' : 'files.dialogs.deleteFile.message',
acceptLabelKey: 'common.buttons.remove',
- onConfirm: () => this.confirmDeleteEntry(file.links.delete),
+ onConfirm: () => this.confirmDeleteEntry(file),
});
}
- confirmDeleteEntry(link: string): void {
- this.deleteEntryAction.emit(link);
+ confirmDeleteEntry(file: FileModel): void {
+ this.deleteEntryAction.emit(file);
}
confirmRename(file: FileModel): void {