1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 14:23:32 +00:00

[PM-2805] Migrate add edit send to Component Library (#6004)

* Converted add-edit send component dialog into a bit-dialog

* Updated Send AddEdit text fields to Component Library

* Migrated Share and Options fields to ComponentLibrary on SendAddEdit

* Migrated footer buttons to ComponentLibrary on SendAddEdit

* Updated web's SendAddEdit component file fields

* Replaced file upload with component library

* Changed SendAddEdit to use Reactive Forms on web

* Changed browser SendAddEdit to use ReactiveForms

* Update SendAddEdit on desktop to use ReactiveForms

* Added AppA11yTitle to button on web SendAddEdit

* Initial efflux-dates web change to ComponentLibrary

* Corrected delete button to check if it is in EditMode on SendAddEdit

* Using BitLink on options button

* Corrected typo on send add edit desktop

* Replaced efflux-dates with datetime-local input on SendAddEdit web, browser and desktop

* Removed efflux dates

* Added firefox custom date popout message on DeletionDate to SendAddEdit browser component

* moved desktop's new send data reload from send to SendAddEdit component

* removing unnecessary attributes and spans from Send AddEdit web

* removed redundant try catch from add edit and unnecessary parameter from close

* Added type for date select options

* Removed unnecessary classes and swapped bootstrap classes by corresponding tailwind classes

* Removed unnecessary code

* Added file as required field
Submit only closes popup on success

* Added pre validations at start of submit

* PM-3668 removed expiration date from required

* PM-3671 not defaulting maximum access count to 0

* PM-3669 Copying the link from link method

* Removed required tag from html and added to formgroup

* PM-3679 Checking if is not EditMode before validating if FormGroup file value is set

* PM-3691 Moved error validation to web component as browser and desktop need to show popup error

* PM-3696 - Disabling hide email when it is unset and has policy to not allow hiding

* PM-3694 - Properly setting default value for dates on Desktop when changing from an existing send

* Disabling hidden required fields

* [PM-3800] Clearing password on new send
This commit is contained in:
aj-rosado
2023-09-07 13:49:13 +01:00
committed by GitHub
parent 86bdfaa7ba
commit 5f78aeaef2
20 changed files with 777 additions and 1388 deletions

View File

@@ -1,5 +1,6 @@
import { DatePipe } from "@angular/common";
import { Directive, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -20,6 +21,23 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { DialogService } from "@bitwarden/components";
// Value = hours
enum DatePreset {
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
ThirtyDays = 720,
Custom = 0,
Never = null,
}
interface DatePresetSelectOption {
name: string;
value: DatePreset;
}
@Directive()
export class AddEditComponent implements OnInit, OnDestroy {
@Input() sendId: string;
@@ -29,12 +47,25 @@ export class AddEditComponent implements OnInit, OnDestroy {
@Output() onDeletedSend = new EventEmitter<SendView>();
@Output() onCancelled = new EventEmitter<SendView>();
deletionDatePresets: DatePresetSelectOption[] = [
{ name: this.i18nService.t("oneHour"), value: DatePreset.OneHour },
{ name: this.i18nService.t("oneDay"), value: DatePreset.OneDay },
{ name: this.i18nService.t("days", "2"), value: DatePreset.TwoDays },
{ name: this.i18nService.t("days", "3"), value: DatePreset.ThreeDays },
{ name: this.i18nService.t("days", "7"), value: DatePreset.SevenDays },
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
{ name: this.i18nService.t("custom"), value: DatePreset.Custom },
];
expirationDatePresets: DatePresetSelectOption[] = [
{ name: this.i18nService.t("never"), value: DatePreset.Never },
...this.deletionDatePresets,
];
copyLink = false;
disableSend = false;
disableHideEmail = false;
send: SendView;
deletionDate: string;
expirationDate: string;
hasPassword: boolean;
password: string;
showPassword = false;
@@ -51,6 +82,27 @@ export class AddEditComponent implements OnInit, OnDestroy {
private sendLinkBaseUrl: string;
private destroy$ = new Subject<void>();
protected formGroup = this.formBuilder.group({
name: ["", Validators.required],
text: [],
textHidden: [false],
fileContents: [],
file: [null, Validators.required],
link: [],
copyLink: false,
maxAccessCount: [],
accessCount: [],
password: [],
notes: [],
hideEmail: false,
disabled: false,
type: [],
defaultExpirationDateTime: [],
defaultDeletionDateTime: ["", Validators.required],
selectedDeletionDatePreset: [DatePreset.SevenDays, Validators.required],
selectedExpirationDatePreset: [],
});
constructor(
protected i18nService: I18nService,
protected platformUtilsService: PlatformUtilsService,
@@ -59,10 +111,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
protected sendService: SendService,
protected messagingService: MessagingService,
protected policyService: PolicyService,
private logService: LogService,
protected logService: LogService,
protected stateService: StateService,
protected sendApiService: SendApiService,
protected dialogService: DialogService
protected dialogService: DialogService,
protected formBuilder: FormBuilder
) {
this.typeOptions = [
{ name: i18nService.t("sendTypeFile"), value: SendType.File },
@@ -72,7 +125,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
get link(): string {
if (this.send.id != null && this.send.accessId != null) {
if (this.send != null && this.send.id != null && this.send.accessId != null) {
return this.sendLinkBaseUrl + this.send.accessId + "/" + this.send.urlB64Key;
}
return null;
@@ -92,13 +145,39 @@ export class AddEditComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableSend = policyAppliesToActiveUser;
if (this.disableSend) {
this.formGroup.disable();
}
});
this.policyService
.policyAppliesToActiveUser$(PolicyType.SendOptions, (p) => p.data.disableHideEmail)
.pipe(takeUntil(this.destroy$))
.subscribe((policyAppliesToActiveUser) => {
this.disableHideEmail = policyAppliesToActiveUser;
if ((this.disableHideEmail = policyAppliesToActiveUser)) {
this.formGroup.controls.hideEmail.disable();
}
});
this.formGroup.controls.type.valueChanges.subscribe((val) => {
this.type = val;
this.typeChanged();
});
this.formGroup.controls.selectedDeletionDatePreset.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((datePreset) => {
datePreset === DatePreset.Custom
? this.formGroup.controls.defaultDeletionDateTime.enable()
: this.formGroup.controls.defaultDeletionDateTime.disable();
});
this.formGroup.controls.hideEmail.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((val) => {
if (!val && this.disableHideEmail) {
this.formGroup.controls.hideEmail.disable();
}
});
await this.load();
@@ -117,29 +196,33 @@ export class AddEditComponent implements OnInit, OnDestroy {
return this.i18nService.t(this.editMode ? "editSend" : "createSend");
}
setDates(event: { deletionDate: string; expirationDate: string }) {
this.deletionDate = event.deletionDate;
this.expirationDate = event.expirationDate;
}
async load() {
this.canAccessPremium = await this.stateService.getCanAccessPremium();
this.emailVerified = await this.stateService.getEmailVerified();
if (!this.canAccessPremium || !this.emailVerified) {
this.type = SendType.Text;
}
this.type = !this.canAccessPremium || !this.emailVerified ? SendType.Text : SendType.File;
if (this.send == null) {
if (this.editMode) {
const send = this.loadSend();
this.send = await send.decrypt();
this.type = this.send.type;
this.updateFormValues();
if (this.send.hideEmail) {
this.formGroup.controls.hideEmail.enable();
}
} else {
this.send = new SendView();
this.send.type = this.type == null ? SendType.File : this.type;
this.send.type = this.type;
this.send.file = new SendFileView();
this.send.text = new SendTextView();
this.send.deletionDate = new Date();
this.send.deletionDate.setDate(this.send.deletionDate.getDate() + 7);
this.formGroup.controls.type.patchValue(this.send.type);
this.formGroup.patchValue({
selectedDeletionDatePreset: DatePreset.SevenDays,
selectedExpirationDatePreset: DatePreset.Never,
});
}
}
@@ -147,6 +230,8 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
async submit(): Promise<boolean> {
this.formGroup.markAllAsTouched();
if (this.disableSend) {
this.platformUtilsService.showToast(
"error",
@@ -156,6 +241,17 @@ export class AddEditComponent implements OnInit, OnDestroy {
return false;
}
this.send.name = this.formGroup.controls.name.value;
this.send.text.text = this.formGroup.controls.text.value;
this.send.text.hidden = this.formGroup.controls.textHidden.value;
this.send.maxAccessCount = this.formGroup.controls.maxAccessCount.value;
this.send.accessCount = this.formGroup.controls.accessCount.value;
this.send.password = this.formGroup.controls.password.value;
this.send.notes = this.formGroup.controls.notes.value;
this.send.hideEmail = this.formGroup.controls.hideEmail.value;
this.send.disabled = this.formGroup.controls.disabled.value;
this.send.type = this.type;
if (this.send.name == null || this.send.name === "") {
this.platformUtilsService.showToast(
"error",
@@ -166,7 +262,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
let file: File = null;
if (this.send.type === SendType.File && !this.editMode) {
if (this.type === SendType.File && !this.editMode) {
const fileEl = document.getElementById("file") as HTMLInputElement;
const files = fileEl.files;
if (files == null || files.length === 0) {
@@ -190,7 +286,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
}
if (this.password != null && this.password.trim() === "") {
if (
this.formGroup.controls.password.value != null &&
this.formGroup.controls.password.value.trim() === ""
) {
this.password = null;
}
@@ -204,7 +303,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.send.accessId = encSend[0].accessId;
}
this.onSavedSend.emit(this.send);
if (this.copyLink && this.link != null) {
if (this.formGroup.controls.copyLink.value && this.link != null) {
await this.handleCopyLinkToClipboard();
return;
}
@@ -227,7 +326,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
return Promise.resolve(this.platformUtilsService.copyToClipboard(link));
}
async delete(): Promise<boolean> {
protected async delete(): Promise<boolean> {
if (this.deletePromise != null) {
return false;
}
@@ -257,7 +356,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
typeChanged() {
if (this.send.type === SendType.File && !this.alertShown) {
if (this.type === SendType.File && !this.alertShown) {
if (!this.canAccessPremium) {
this.alertShown = true;
this.messagingService.send("premiumRequired");
@@ -266,6 +365,9 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.messagingService.send("emailVerificationRequired");
}
}
this.type === SendType.Text || this.editMode
? this.formGroup.controls.file.disable()
: this.formGroup.controls.file.enable();
}
toggleOptions() {
@@ -277,17 +379,23 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
protected async encryptSend(file: File): Promise<[Send, EncArrayBuffer]> {
const sendData = await this.sendService.encrypt(this.send, file, this.password, null);
const sendData = await this.sendService.encrypt(
this.send,
file,
this.formGroup.controls.password.value,
null
);
// Parse dates
try {
sendData[0].deletionDate = this.deletionDate == null ? null : new Date(this.deletionDate);
sendData[0].deletionDate =
this.formattedDeletionDate == null ? null : new Date(this.formattedDeletionDate);
} catch {
sendData[0].deletionDate = null;
}
try {
sendData[0].expirationDate =
this.expirationDate == null ? null : new Date(this.expirationDate);
this.formattedExpirationDate == null ? null : new Date(this.formattedExpirationDate);
} catch {
sendData[0].expirationDate = null;
}
@@ -299,6 +407,34 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.showPassword = !this.showPassword;
document.getElementById("password").focus();
}
updateFormValues() {
this.formGroup.patchValue({
name: this.send?.name ?? "",
text: this.send?.text?.text ?? "",
textHidden: this.send?.text?.hidden ?? false,
link: this.link ?? "",
maxAccessCount: this.send?.maxAccessCount,
accessCount: this.send?.accessCount ?? 0,
notes: this.send?.notes ?? "",
hideEmail: this.send?.hideEmail ?? false,
disabled: this.send?.disabled ?? false,
type: this.send.type ?? this.type,
password: "",
selectedDeletionDatePreset: this.editMode ? DatePreset.Custom : DatePreset.SevenDays,
selectedExpirationDatePreset: this.editMode ? DatePreset.Custom : DatePreset.Never,
defaultExpirationDateTime:
this.send.expirationDate != null
? this.datePipe.transform(new Date(this.send.expirationDate), "yyyy-MM-ddTHH:mm")
: null,
defaultDeletionDateTime: this.datePipe.transform(
new Date(this.send.deletionDate),
"yyyy-MM-ddTHH:mm"
),
});
}
private async handleCopyLinkToClipboard() {
const copySuccess = await this.copyLinkToClipboard(this.link);
if (copySuccess ?? true) {
@@ -319,4 +455,46 @@ export class AddEditComponent implements OnInit, OnDestroy {
await this.copyLinkToClipboard(this.link);
}
}
clearExpiration() {
this.formGroup.controls.defaultExpirationDateTime.patchValue(null);
}
get formattedExpirationDate(): string {
switch (this.formGroup.controls.selectedExpirationDatePreset.value as DatePreset) {
case DatePreset.Never:
return null;
case DatePreset.Custom:
if (!this.formGroup.controls.defaultExpirationDateTime.value) {
return null;
}
return this.formGroup.controls.defaultExpirationDateTime.value;
default: {
const now = new Date();
const milliseconds = now.setTime(
now.getTime() +
(this.formGroup.controls.selectedExpirationDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(milliseconds).toString();
}
}
}
get formattedDeletionDate(): string {
switch (this.formGroup.controls.selectedDeletionDatePreset.value as DatePreset) {
case DatePreset.Never:
this.formGroup.controls.selectedDeletionDatePreset.patchValue(DatePreset.SevenDays);
return this.formattedDeletionDate;
case DatePreset.Custom:
return this.formGroup.controls.defaultDeletionDateTime.value;
default: {
const now = new Date();
const milliseconds = now.setTime(
now.getTime() +
(this.formGroup.controls.selectedDeletionDatePreset.value as number) * 60 * 60 * 1000
);
return new Date(milliseconds).toString();
}
}
}
}