mirror of
https://github.com/bitwarden/clients.git
synced 2025-12-22 13:17:30 +00:00
[PM-23562] Prevent closing dialog and window when uploading an attachment (#17287)
* Prevent users from cancelling an in-flight upload, and attempt to block them from closing the window. * Add comment for deprecated event.returnValue
This commit is contained in:
@@ -108,11 +108,21 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() submitBtn?: ButtonComponent;
|
||||
|
||||
/** Emits when a file upload is started */
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output() onUploadStarted = new EventEmitter<void>();
|
||||
|
||||
/** Emits after a file has been successfully uploaded */
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output() onUploadSuccess = new EventEmitter<void>();
|
||||
|
||||
/** Emits when a file upload fails */
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output() onUploadFailed = new EventEmitter<void>();
|
||||
|
||||
/** Emits after a file has been successfully removed */
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@@ -196,6 +206,8 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
/** Save the attachments to the cipher */
|
||||
submit = async () => {
|
||||
this.onUploadStarted.emit();
|
||||
|
||||
const file = this.attachmentForm.value.file;
|
||||
if (file === null) {
|
||||
this.toastService.showToast({
|
||||
@@ -253,6 +265,7 @@ export class CipherAttachmentsComponent implements OnInit, AfterViewInit {
|
||||
variant: "error",
|
||||
message: errorMessage,
|
||||
});
|
||||
this.onUploadFailed.emit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
[organizationId]="organizationId"
|
||||
[admin]="admin"
|
||||
[submitBtn]="submitBtn"
|
||||
(onUploadStarted)="uploadStarted()"
|
||||
(onUploadSuccess)="uploadSuccessful()"
|
||||
(onUploadFailed)="uploadFailed()"
|
||||
(onRemoveSuccess)="removalSuccessful()"
|
||||
></app-cipher-attachments>
|
||||
</ng-container>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { Component, HostListener, Inject } from "@angular/core";
|
||||
|
||||
import { CipherId, OrganizationId } from "@bitwarden/common/types/guid";
|
||||
import { UnionOfValues } from "@bitwarden/common/vault/types/union-of-values";
|
||||
@@ -52,6 +52,7 @@ export class AttachmentsV2Component {
|
||||
admin: boolean = false;
|
||||
organizationId?: OrganizationId;
|
||||
attachmentFormId = CipherAttachmentsComponent.attachmentFormID;
|
||||
private isUploading = false;
|
||||
|
||||
/**
|
||||
* Constructor for AttachmentsV2Component.
|
||||
@@ -82,16 +83,54 @@ export class AttachmentsV2Component {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent browser tab from closing/refreshing during upload.
|
||||
* Shows a confirmation dialog if user tries to leave during an active upload.
|
||||
* This provides additional protection beyond dialogRef.disableClose.
|
||||
* Using arrow function to preserve 'this' context when used as event listener.
|
||||
*/
|
||||
@HostListener("window:beforeunload", ["$event"])
|
||||
private handleBeforeUnloadEvent = (event: BeforeUnloadEvent): string | undefined => {
|
||||
if (this.isUploading) {
|
||||
event.preventDefault();
|
||||
// The custom message is not displayed in modern browsers, but MDN docs still recommend setting it for legacy support.
|
||||
const message = "Upload in progress. Are you sure you want to leave?";
|
||||
event.returnValue = message;
|
||||
return message;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when an attachment upload is started.
|
||||
* Disables closing the dialog to prevent accidental interruption.
|
||||
*/
|
||||
uploadStarted() {
|
||||
this.isUploading = true;
|
||||
this.dialogRef.disableClose = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an attachment is successfully uploaded.
|
||||
* Closes the dialog with an 'uploaded' result.
|
||||
* Re-enables dialog closing and closes the dialog with an 'uploaded' result.
|
||||
*/
|
||||
uploadSuccessful() {
|
||||
this.isUploading = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
this.dialogRef.close({
|
||||
action: AttachmentDialogResult.Uploaded,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an attachment upload fails.
|
||||
* Re-enables closing the dialog.
|
||||
*/
|
||||
uploadFailed() {
|
||||
this.isUploading = false;
|
||||
this.dialogRef.disableClose = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an attachment is successfully removed.
|
||||
* Closes the dialog with a 'removed' result.
|
||||
|
||||
Reference in New Issue
Block a user