1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

[PM-9869] Create SendFormContainer (#10147)

* Move SendV2component into send-v2 subFolder

* Create SendFormContainer and related services

* Add initial SendFormComponent which uses the SendFormContainer

* Remove AdditionalOptionsSectionComponent which will be added with a future PR

* Add libs/tools/send to root tsconfig

* Register libs/tools/send/send-ui with root jest.config.js

* Register libs/tools/send/send-ui with root tailwind.config.js

* Fix service injection on DefaultSendFormService

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Daniel James Smith
2024-07-19 21:17:52 +02:00
committed by GitHub
parent beeb0354fd
commit 1320d96cb4
20 changed files with 630 additions and 1 deletions

View File

@@ -0,0 +1,4 @@
<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>
</form>

View File

@@ -0,0 +1,210 @@
import { NgIf } from "@angular/common";
import {
AfterViewInit,
Component,
DestroyRef,
EventEmitter,
forwardRef,
inject,
Input,
OnChanges,
OnInit,
Output,
ViewChild,
} from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
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 {
AsyncActionsModule,
BitSubmitDirective,
ButtonComponent,
CardComponent,
FormFieldModule,
ItemModule,
SectionComponent,
SelectModule,
ToastService,
TypographyModule,
} from "@bitwarden/components";
import { SendFormConfig } from "../abstractions/send-form-config.service";
import { SendFormService } from "../abstractions/send-form.service";
import { SendForm, SendFormContainer } from "../send-form-container";
@Component({
selector: "tools-send-form",
templateUrl: "./send-form.component.html",
standalone: true,
providers: [
{
provide: SendFormContainer,
useExisting: forwardRef(() => SendFormComponent),
},
],
imports: [
AsyncActionsModule,
CardComponent,
SectionComponent,
TypographyModule,
ItemModule,
FormFieldModule,
ReactiveFormsModule,
SelectModule,
NgIf,
],
})
export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, SendFormContainer {
@ViewChild(BitSubmitDirective)
private bitSubmit: BitSubmitDirective;
private destroyRef = inject(DestroyRef);
private _firstInitialized = false;
/**
* The form ID to use for the form. Used to connect it to a submit button.
*/
@Input({ required: true }) formId: string;
/**
* The configuration for the add/edit form. Used to determine which controls are shown and what values are available.
*/
@Input({ required: true }) config: SendFormConfig;
/**
* Optional submit button that will be disabled or marked as loading when the form is submitting.
*/
@Input()
submitBtn?: ButtonComponent;
/**
* Event emitted when the send is saved successfully.
*/
@Output() sendSaved = new EventEmitter<SendView>();
/**
* The original send being edited or cloned. Null for add mode.
*/
originalSendView: SendView | null;
/**
* The form group for the send. Starts empty and is populated by child components via the `registerChildForm` method.
* @protected
*/
protected sendForm = this.formBuilder.group<SendForm>({});
/**
* The value of the updated send. Starts as a new send and is updated
* by child components via the `patchSend` method.
* @protected
*/
protected updatedSendView: SendView | null;
protected loading: boolean = true;
SendType = SendType;
ngAfterViewInit(): void {
if (this.submitBtn) {
this.bitSubmit.loading$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((loading) => {
this.submitBtn.loading = loading;
});
this.bitSubmit.disabled$.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((disabled) => {
this.submitBtn.disabled = disabled;
});
}
}
/**
* Registers a child form group with the parent form group. Used by child components to add their form groups to
* the parent form for validation.
* @param name - The name of the form group.
* @param group - The form group to add.
*/
registerChildForm<K extends keyof SendForm>(
name: K,
group: Exclude<SendForm[K], undefined>,
): void {
this.sendForm.setControl(name, group);
}
/**
* Patches the updated send with the provided partial senbd. Used by child components to update the send
* as their form values change.
* @param send
*/
patchSend(send: Partial<SendView>): void {
this.updatedSendView = Object.assign(this.updatedSendView, send);
}
/**
* We need to re-initialize the form when the config is updated.
*/
async ngOnChanges() {
// Avoid re-initializing the form on the first change detection cycle.
if (this._firstInitialized) {
await this.init();
}
}
async ngOnInit() {
await this.init();
this._firstInitialized = true;
}
async init() {
this.loading = true;
this.updatedSendView = new SendView();
this.originalSendView = null;
this.sendForm.reset();
if (this.config == null) {
return;
}
if (this.config.mode !== "add") {
if (this.config.originalSend == null) {
throw new Error("Original send is required for edit or clone mode");
}
this.originalSendView = await this.addEditFormService.decryptSend(this.config.originalSend);
this.updatedSendView = Object.assign(this.updatedSendView, this.originalSendView);
} else {
this.updatedSendView.type = this.config.sendType;
}
this.loading = false;
}
constructor(
private formBuilder: FormBuilder,
private addEditFormService: SendFormService,
private toastService: ToastService,
private i18nService: I18nService,
) {}
submit = async () => {
if (this.sendForm.invalid) {
this.sendForm.markAllAsTouched();
return;
}
// TODO: Add file handling
await this.addEditFormService.saveSend(this.updatedSendView, null, this.config);
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t(
this.config.mode === "edit" || this.config.mode === "partial-edit"
? "editedItem"
: "addedItem",
),
});
this.sendSaved.emit(this.updatedSendView);
};
}