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:
committed by
GitHub
parent
beeb0354fd
commit
1320d96cb4
@@ -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>
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user