mirror of
https://github.com/bitwarden/browser.git
synced 2024-12-22 16:29:09 +01:00
[PM-11899] - send text details component (#11002)
* Temporary local changes not meant to be merged
* WIP - send text details
* send text details
* remove extraneous code
* create base send details component
* remove file components
* fix send text details form
* remove comments
* fix send text details component
* revert type changes
* send created redirect
* Revert "send created redirect"
This reverts commit 36711d54a3
.
* Removed hint under textArea, as per design
* Removed unused message keys
---------
Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
parent
1b7bb014d2
commit
f8fc6269f2
@ -2268,6 +2268,10 @@
|
||||
"message": "Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendDetails": {
|
||||
"message": "Send details",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"searchSends": {
|
||||
"message": "Search Sends",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
@ -2279,6 +2283,9 @@
|
||||
"sendTypeText": {
|
||||
"message": "Text"
|
||||
},
|
||||
"sendTypeTextToShare": {
|
||||
"message": "Text to share"
|
||||
},
|
||||
"sendTypeFile": {
|
||||
"message": "File"
|
||||
},
|
||||
@ -2286,6 +2293,9 @@
|
||||
"message": "All Sends",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"hideTextByDefault": {
|
||||
"message": "Hide text by default"
|
||||
},
|
||||
"maxAccessCountReached": {
|
||||
"message": "Max access count reached",
|
||||
"description": "This text will be displayed after a Send has been accessed the maximum amount of times."
|
||||
@ -2359,6 +2369,10 @@
|
||||
"message": "The Send will be permanently deleted on the specified date and time.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"deletionDateDescV2": {
|
||||
"message": "The Send will be permanently deleted on this date.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"expirationDate": {
|
||||
"message": "Expiration date"
|
||||
},
|
||||
|
@ -57,6 +57,8 @@ export class SendService implements InternalSendServiceAbstraction {
|
||||
send.disabled = model.disabled;
|
||||
send.hideEmail = model.hideEmail;
|
||||
send.maxAccessCount = model.maxAccessCount;
|
||||
send.deletionDate = model.deletionDate;
|
||||
send.expirationDate = model.expirationDate;
|
||||
if (model.key == null) {
|
||||
const key = await this.keyGenerationService.createKeyWithPurpose(
|
||||
128,
|
||||
|
@ -0,0 +1,111 @@
|
||||
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 type BaseSendDetailsForm = FormGroup<{
|
||||
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: BaseSendDetailsForm;
|
||||
customDeletionDateOption: DatePresetSelectOption | null = null;
|
||||
datePresetOptions: DatePresetSelectOption[] = [];
|
||||
|
||||
constructor(
|
||||
protected sendFormContainer: SendFormContainer,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected i18nService: I18nService,
|
||||
protected datePipe: DatePipe,
|
||||
) {
|
||||
this.sendDetailsForm = this.formBuilder.group({
|
||||
name: new FormControl("", Validators.required),
|
||||
selectedDeletionDatePreset: new FormControl(DatePreset.SevenDays || "", Validators.required),
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
<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"
|
||||
[sendDetailsForm]="sendDetailsForm"
|
||||
></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>
|
@ -0,0 +1,59 @@
|
||||
import { CommonModule, DatePipe } from "@angular/common";
|
||||
import { Component, 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 {
|
||||
SectionComponent,
|
||||
SectionHeaderComponent,
|
||||
TypographyModule,
|
||||
CardComponent,
|
||||
FormFieldModule,
|
||||
IconButtonModule,
|
||||
CheckboxModule,
|
||||
SelectModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<bit-section [formGroup]="sendTextDetailsForm">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "sendTypeTextToShare" | i18n }}</bit-label>
|
||||
<textarea bitInput id="text" rows="6" formControlName="text"></textarea>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="hidden" />
|
||||
<bit-label>{{ "hideTextByDefault" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</bit-section>
|
@ -0,0 +1,82 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import {
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
Validators,
|
||||
ReactiveFormsModule,
|
||||
} from "@angular/forms";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import { CheckboxModule, FormFieldModule, SectionComponent } from "@bitwarden/components";
|
||||
|
||||
import { SendFormConfig } from "../../abstractions/send-form-config.service";
|
||||
import { SendFormContainer } from "../../send-form-container";
|
||||
|
||||
import { BaseSendDetailsForm } from "./base-send-details.component";
|
||||
|
||||
type BaseSendTextDetailsForm = FormGroup<{
|
||||
text: FormControl<string>;
|
||||
hidden: FormControl<boolean>;
|
||||
}>;
|
||||
|
||||
export type SendTextDetailsForm = BaseSendTextDetailsForm & BaseSendDetailsForm;
|
||||
|
||||
@Component({
|
||||
selector: "tools-send-text-details",
|
||||
templateUrl: "./send-text-details.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CheckboxModule,
|
||||
CommonModule,
|
||||
JslibModule,
|
||||
ReactiveFormsModule,
|
||||
FormFieldModule,
|
||||
SectionComponent,
|
||||
],
|
||||
})
|
||||
export class SendTextDetailsComponent implements OnInit {
|
||||
@Input() config: SendFormConfig;
|
||||
@Input() originalSendView?: SendView;
|
||||
@Input() sendDetailsForm: BaseSendDetailsForm;
|
||||
|
||||
baseSendTextDetailsForm: BaseSendTextDetailsForm;
|
||||
sendTextDetailsForm: SendTextDetailsForm;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
protected sendFormContainer: SendFormContainer,
|
||||
) {
|
||||
this.baseSendTextDetailsForm = this.formBuilder.group({
|
||||
text: new FormControl("", Validators.required),
|
||||
hidden: new FormControl(false),
|
||||
});
|
||||
|
||||
this.sendTextDetailsForm = Object.assign(this.baseSendTextDetailsForm, this.sendDetailsForm);
|
||||
|
||||
this.sendFormContainer.registerChildForm("sendTextDetailsForm", this.sendTextDetailsForm);
|
||||
|
||||
this.sendTextDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
|
||||
this.sendFormContainer.patchSend((send) => {
|
||||
return Object.assign(send, {
|
||||
text: {
|
||||
text: value.text,
|
||||
hidden: value.hidden,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.originalSendView) {
|
||||
this.baseSendTextDetailsForm.patchValue({
|
||||
text: this.originalSendView.text?.text || "",
|
||||
hidden: this.originalSendView.text?.hidden || false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +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"> </ng-container>
|
||||
<ng-container *ngIf="!loading">
|
||||
<tools-send-details
|
||||
[config]="config"
|
||||
[originalSendView]="originalSendView"
|
||||
></tools-send-details>
|
||||
</ng-container>
|
||||
</form>
|
||||
|
@ -35,6 +35,8 @@ import { SendFormConfig } from "../abstractions/send-form-config.service";
|
||||
import { SendFormService } from "../abstractions/send-form.service";
|
||||
import { SendForm, SendFormContainer } from "../send-form-container";
|
||||
|
||||
import { SendDetailsComponent } from "./send-details/send-details.component";
|
||||
|
||||
@Component({
|
||||
selector: "tools-send-form",
|
||||
templateUrl: "./send-form.component.html",
|
||||
@ -55,6 +57,7 @@ import { SendForm, SendFormContainer } from "../send-form-container";
|
||||
ReactiveFormsModule,
|
||||
SelectModule,
|
||||
NgIf,
|
||||
SendDetailsComponent,
|
||||
],
|
||||
})
|
||||
export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer {
|
||||
@ -131,12 +134,11 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches the updated send with the provided partial senbd. Used by child components to update the send
|
||||
* as their form values change.
|
||||
* @param send
|
||||
* Method to update the sendView with the new values. This method should be called by the child form components
|
||||
* @param updateFn - A function that takes the current sendView and returns the updated sendView
|
||||
*/
|
||||
patchSend(send: Partial<SendView>): void {
|
||||
this.updatedSendView = Object.assign(this.updatedSendView, send);
|
||||
patchSend(updateFn: (current: SendView) => SendView): void {
|
||||
this.updatedSendView = updateFn(this.updatedSendView);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,11 +1,16 @@
|
||||
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
|
||||
import { SendFormConfig } from "./abstractions/send-form-config.service";
|
||||
import { SendDetailsComponent } from "./components/send-details/send-details.component";
|
||||
import { SendTextDetailsForm } from "./components/send-details/send-text-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 = object;
|
||||
export type SendForm = {
|
||||
sendDetailsForm?: SendDetailsComponent["sendDetailsForm"];
|
||||
sendTextDetailsForm?: SendTextDetailsForm;
|
||||
};
|
||||
|
||||
/**
|
||||
* A container for the {@link SendForm} that allows for registration of child form groups and patching of the send
|
||||
@ -32,5 +37,5 @@ export abstract class SendFormContainer {
|
||||
group: Exclude<SendForm[K], undefined>,
|
||||
): void;
|
||||
|
||||
abstract patchSend(send: Partial<SendView>): void;
|
||||
abstract patchSend(updateFn: (current: SendView) => SendView): void;
|
||||
}
|
||||
|
@ -17,13 +17,8 @@ export class DefaultSendFormService implements SendFormService {
|
||||
return await send.decrypt();
|
||||
}
|
||||
|
||||
async saveSend(
|
||||
send: SendView,
|
||||
file: File | ArrayBuffer,
|
||||
config: SendFormConfig,
|
||||
): Promise<SendView> {
|
||||
async saveSend(send: SendView, file: File | ArrayBuffer, config: SendFormConfig) {
|
||||
const sendData = await this.sendService.encrypt(send, file, send.password, null);
|
||||
const savedSend = await this.sendApiService.save(sendData);
|
||||
return await savedSend.decrypt();
|
||||
return await this.sendApiService.save(sendData);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user