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

create base send details component

This commit is contained in:
jaasen-livefront
2024-09-11 17:43:32 -07:00
parent 573353116b
commit d4c149a4fb
10 changed files with 230 additions and 178 deletions

View File

@@ -0,0 +1,112 @@
import { DatePipe } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, FormGroup, FormControl, Validators } from "@angular/forms";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendFormConfig } from "../../abstractions/send-form-config.service";
import { SendFormContainer } from "../../send-form-container";
export interface BaseSendDetailsForm {
name: FormControl<string>;
selectedDeletionDatePreset: FormControl<string | number>;
}
// Value = hours
export enum DatePreset {
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
FourteenDays = 336,
ThirtyDays = 720,
}
export interface DatePresetSelectOption {
name: string;
value: DatePreset | string;
}
@Component({
selector: "base-send-details-behavior",
template: "",
})
export class BaseSendDetailsComponent implements OnInit {
@Input() config: SendFormConfig;
@Input() originalSendView: SendView;
sendDetailsForm: FormGroup<BaseSendDetailsForm>;
customDeletionDateOption: DatePresetSelectOption | null = null;
datePresetOptions: DatePresetSelectOption[] = [];
constructor(
protected sendFormContainer: SendFormContainer,
protected formBuilder: FormBuilder,
protected i18nService: I18nService,
protected datePipe: DatePipe,
) {
// Initialize the form
this.sendDetailsForm = this.formBuilder.group({
name: new FormControl("", Validators.required),
selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required),
}) as FormGroup<BaseSendDetailsForm>;
this.sendDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.sendFormContainer.patchSend((send) => {
return Object.assign(send, {
name: value.name,
deletionDate: new Date(this.formattedDeletionDate),
expirationDate: new Date(this.formattedDeletionDate),
} as SendView);
});
});
}
async ngOnInit() {
this.setupDeletionDatePresets();
if (this.originalSendView) {
this.sendDetailsForm.patchValue({
name: this.originalSendView.name,
selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(),
});
if (this.originalSendView.deletionDate) {
this.customDeletionDateOption = {
name: this.datePipe.transform(this.originalSendView.deletionDate, "MM/dd/yyyy, hh:mm a"),
value: this.originalSendView.deletionDate.toString(),
};
this.datePresetOptions.unshift(this.customDeletionDateOption);
}
}
}
setupDeletionDatePresets() {
const defaultSelections: 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", "14"), value: DatePreset.FourteenDays },
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
];
this.datePresetOptions = defaultSelections;
}
get formattedDeletionDate(): string {
const now = new Date();
const selectedValue = this.sendDetailsForm.controls.selectedDeletionDatePreset.value;
if (typeof selectedValue === "string") {
return selectedValue;
}
const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000);
return new Date(milliseconds).toString();
}
}

View File

@@ -0,0 +1,34 @@
<bit-section [formGroup]="sendDetailsForm">
<bit-section-header>
<h2 bitTypography="h5">{{ "sendDetails" | i18n }}</h2>
</bit-section-header>
<bit-card>
<bit-form-field>
<bit-label>{{ "name" | i18n }}</bit-label>
<input bitInput type="text" formControlName="name" />
</bit-form-field>
<tools-send-text-details
*ngIf="config.sendType === TextSendType"
[config]="config"
[originalSendView]="originalSendView"
></tools-send-text-details>
<bit-form-field>
<bit-label>{{ "deletionDate" | i18n }}</bit-label>
<bit-select
id="deletionDate"
name="SelectedDeletionDatePreset"
formControlName="selectedDeletionDatePreset"
>
<bit-option
*ngFor="let o of datePresetOptions"
[value]="o.value"
[label]="o.name"
></bit-option>
</bit-select>
<bit-hint>{{ "deletionDateDescV2" | i18n }}</bit-hint>
</bit-form-field>
</bit-card>
</bit-section>

View File

@@ -0,0 +1,64 @@
import { CommonModule, DatePipe } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import {
SectionComponent,
SectionHeaderComponent,
TypographyModule,
CardComponent,
FormFieldModule,
IconButtonModule,
CheckboxModule,
SelectModule,
} from "@bitwarden/components";
import { SendFormConfig } from "../../abstractions/send-form-config.service";
import { SendFormContainer } from "../../send-form-container";
import { BaseSendDetailsComponent } from "./base-send-details.component";
import { SendTextDetailsComponent } from "./send-text-details.component";
@Component({
selector: "tools-send-details",
templateUrl: "./send-details.component.html",
standalone: true,
imports: [
SectionComponent,
SectionHeaderComponent,
TypographyModule,
JslibModule,
CardComponent,
FormFieldModule,
ReactiveFormsModule,
SendTextDetailsComponent,
IconButtonModule,
CheckboxModule,
CommonModule,
SelectModule,
],
})
export class SendDetailsComponent extends BaseSendDetailsComponent implements OnInit {
@Input() config: SendFormConfig;
@Input() originalSendView: SendView;
FileSendType = SendType.File;
TextSendType = SendType.Text;
constructor(
protected sendFormContainer: SendFormContainer,
protected formBuilder: FormBuilder,
protected i18nService: I18nService,
protected datePipe: DatePipe,
) {
super(sendFormContainer, formBuilder, i18nService, datePipe);
}
async ngOnInit() {
await super.ngOnInit();
}
}

View File

@@ -1,38 +1,9 @@
<bit-section [formGroup]="sendTextDetailsForm">
<bit-section-header>
<h2 bitTypography="h5">{{ "sendDetails" | i18n }}</h2>
</bit-section-header>
<bit-card>
<bit-form-field>
<bit-label>{{ "name" | i18n }}</bit-label>
<input bitInput type="text" formControlName="name" />
</bit-form-field>
<bit-form-field>
<bit-label>{{ "sendTypeTextToShare" | i18n }}</bit-label>
<textarea bitInput id="text" rows="6" formControlName="textToShare"></textarea>
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
</bit-form-field>
<bit-form-control>
<input bitCheckbox type="checkbox" formControlName="hideTextByDefault" />
<bit-label>{{ "hideTextByDefault" | i18n }}</bit-label>
</bit-form-control>
<bit-form-field>
<bit-label>{{ "deletionDate" | i18n }}</bit-label>
<bit-select
id="deletionDate"
name="SelectedDeletionDatePreset"
formControlName="selectedDeletionDatePreset"
>
<bit-option
*ngFor="let o of datePresetOptions"
[value]="o.value"
[label]="o.name"
></bit-option>
</bit-select>
<bit-hint>{{ "deletionDateDescV2" | i18n }}</bit-hint>
</bit-form-field>
</bit-card>
</bit-section>
<bit-form-field>
<bit-label>{{ "sendTypeTextToShare" | i18n }}</bit-label>
<textarea bitInput id="text" rows="6" formControlName="textToShare"></textarea>
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
</bit-form-field>
<bit-form-control>
<input bitCheckbox type="checkbox" formControlName="hideTextByDefault" />
<bit-label>{{ "hideTextByDefault" | i18n }}</bit-label>
</bit-form-control>

View File

@@ -1,142 +1,15 @@
import { CommonModule, DatePipe } from "@angular/common";
import { Component, Input, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { Component, Input } from "@angular/core";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import {
CardComponent,
CheckboxModule,
FormFieldModule,
IconButtonModule,
SectionComponent,
SectionHeaderComponent,
SelectModule,
TypographyModule,
} from "@bitwarden/components";
import { SendFormConfig } from "../../abstractions/send-form-config.service";
import { SendFormContainer } from "../../send-form-container";
// Value = hours
enum DatePreset {
OneHour = 1,
OneDay = 24,
TwoDays = 48,
ThreeDays = 72,
SevenDays = 168,
FourteenDays = 336,
ThirtyDays = 720,
}
interface DatePresetSelectOption {
name: string;
value: DatePreset | string;
}
@Component({
selector: "tools-send-text-details",
templateUrl: "./send-text-details.component.html",
template: "send-text-details.component.html",
standalone: true,
imports: [
SectionComponent,
SectionHeaderComponent,
TypographyModule,
JslibModule,
CardComponent,
FormFieldModule,
ReactiveFormsModule,
IconButtonModule,
CheckboxModule,
CommonModule,
SelectModule,
],
})
export class SendTextDetailsComponent implements OnInit {
@Input({ required: true })
config: SendFormConfig;
@Input()
originalSendView: SendView;
sendTextDetailsForm = this.formBuilder.group({
name: ["", [Validators.required]],
textToShare: [""],
hideTextByDefault: [false],
selectedDeletionDatePreset: [DatePreset.SevenDays || "", Validators.required],
});
customDeletionDateOption: DatePresetSelectOption | null = null;
datePresetOptions: DatePresetSelectOption[] = [];
constructor(
private sendFormContainer: SendFormContainer,
private formBuilder: FormBuilder,
private i18nService: I18nService,
protected datePipe: DatePipe,
) {
this.sendFormContainer.registerChildForm("sendTextDetailsForm", this.sendTextDetailsForm);
this.sendTextDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.sendFormContainer.patchSend((send) => {
return Object.assign(send, {
name: value.name,
text: { text: value.textToShare, hidden: value.hideTextByDefault },
deletionDate: new Date(this.formattedDeletionDate),
expirationDate: new Date(this.formattedDeletionDate),
} as SendView);
});
});
}
async ngOnInit() {
this.setupDeletionDatePresets();
if (this.originalSendView) {
this.sendTextDetailsForm.patchValue({
name: this.originalSendView.name,
textToShare: this.originalSendView.text.text,
hideTextByDefault: this.originalSendView.text.hidden,
selectedDeletionDatePreset: this.originalSendView.deletionDate.toString(),
});
// If existing deletion date exists, calculate it once and store it
if (this.originalSendView.deletionDate) {
this.customDeletionDateOption = {
name: this.datePipe.transform(this.originalSendView.deletionDate, "MM/dd/yyyy, hh:mm a"),
value: this.originalSendView.deletionDate.toString(),
};
this.datePresetOptions.unshift(this.customDeletionDateOption);
}
}
}
setupDeletionDatePresets() {
const defaultSelections: 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", "14"), value: DatePreset.FourteenDays },
{ name: this.i18nService.t("days", "30"), value: DatePreset.ThirtyDays },
];
this.datePresetOptions = defaultSelections;
}
get formattedDeletionDate(): string {
const now = new Date();
const selectedValue = this.sendTextDetailsForm.controls.selectedDeletionDatePreset.value;
// If existing deletion date is selected, return it as is
if (typeof selectedValue === "string") {
return selectedValue;
}
const milliseconds = now.setTime(now.getTime() + (selectedValue as number) * 60 * 60 * 1000);
return new Date(milliseconds).toString();
}
export class SendTextDetailsComponent {
@Input() config: SendFormConfig;
@Input() originalSendView: SendView;
}

View File

@@ -1,10 +1,8 @@
<form [id]="formId" [formGroup]="sendForm" [bitSubmit]="submit">
<!-- TODO: Should we show a loading spinner here? Or emit a ready event for the container to handle loading state -->
<ng-container *ngIf="!loading">
<tools-send-text-details
*ngIf="config.sendType === SendType.Text"
<tools-send-details
[config]="config"
[originalSendView]="originalSendView"
></tools-send-text-details>
></tools-send-details>
</ng-container>
</form>

View File

@@ -35,7 +35,7 @@ import { SendFormConfig } from "../abstractions/send-form-config.service";
import { SendFormService } from "../abstractions/send-form.service";
import { SendForm, SendFormContainer } from "../send-form-container";
import { SendTextDetailsComponent } from "./send-details/send-text-details.component";
import { SendDetailsComponent } from "./send-details/send-details.component";
@Component({
selector: "tools-send-form",
@@ -57,7 +57,7 @@ import { SendTextDetailsComponent } from "./send-details/send-text-details.compo
ReactiveFormsModule,
SelectModule,
NgIf,
SendTextDetailsComponent,
SendDetailsComponent,
],
})
export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer {

View File

@@ -1,13 +1,13 @@
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendFormConfig } from "./abstractions/send-form-config.service";
import { SendTextDetailsComponent } from "./components/send-details/send-text-details.component";
import { SendDetailsComponent } from "./components/send-details/send-details.component";
/**
* The complete form for a send. Includes all the sub-forms from their respective section components.
* TODO: Add additional form sections as they are implemented.
*/
export type SendForm = {
sendTextDetailsForm?: SendTextDetailsComponent["sendTextDetailsForm"];
sendDetailsForm?: SendDetailsComponent["sendDetailsForm"];
};
/**