[PM-11903] - add file send component (#11132)

* wip - send file details

* wip - file send

* send file details

* fix click on send list container

* remove popup code

* remove popup code

* finalize send file details

* address PR feedback. add base form to send form

* revert changes to send list items container

* revert changes to send list items container

---------

Co-authored-by: Daniel James Smith <2670567+djsmith85@users.noreply.github.com>
This commit is contained in:
Jordan Aasen 2024-09-18 14:36:53 -07:00 committed by GitHub
parent 2b85392b0f
commit 00f2317a82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 152 additions and 15 deletions

View File

@ -1136,6 +1136,9 @@
"file": {
"message": "File"
},
"fileToShare": {
"message": "File to share"
},
"selectFile": {
"message": "Select a file"
},

View File

@ -117,25 +117,18 @@ export class SendAddEditComponent {
)
.subscribe((config) => {
this.config = config;
this.headerText = this.getHeaderText(config.mode, config.sendType);
this.headerText = this.getHeaderText(config.mode);
});
}
/**
* Gets the header text based on the mode and type.
* Gets the header text based on the mode.
* @param mode The mode of the send form.
* @param type The type of the send form.
* @returns The header text.
*/
private getHeaderText(mode: SendFormMode, type: SendType) {
const headerKey =
mode === "edit" || mode === "partial-edit" ? "editItemHeader" : "newItemHeader";
switch (type) {
case SendType.Text:
return this.i18nService.t(headerKey, this.i18nService.t("sendTypeText"));
case SendType.File:
return this.i18nService.t(headerKey, this.i18nService.t("sendTypeFile"));
}
private getHeaderText(mode: SendFormMode) {
return this.i18nService.t(
mode === "edit" || mode === "partial-edit" ? "editSend" : "createSend",
);
}
}

View File

@ -62,6 +62,8 @@ export class BaseSendDetailsComponent implements OnInit {
} as SendView);
});
});
this.sendFormContainer.registerChildForm("sendDetailsForm", this.sendDetailsForm);
}
async ngOnInit() {

View File

@ -16,6 +16,13 @@
[sendDetailsForm]="sendDetailsForm"
></tools-send-text-details>
<tools-send-file-details
*ngIf="config.sendType === FileSendType"
[config]="config"
[originalSendView]="originalSendView"
[sendDetailsForm]="sendDetailsForm"
></tools-send-file-details>
<bit-form-field>
<bit-label>{{ "deletionDate" | i18n }}</bit-label>
<bit-select

View File

@ -19,6 +19,7 @@ import {
import { SendFormContainer } from "../../send-form-container";
import { BaseSendDetailsComponent } from "./base-send-details.component";
import { SendFileDetailsComponent } from "./send-file-details.component";
import { SendTextDetailsComponent } from "./send-text-details.component";
@Component({
@ -34,6 +35,7 @@ import { SendTextDetailsComponent } from "./send-text-details.component";
FormFieldModule,
ReactiveFormsModule,
SendTextDetailsComponent,
SendFileDetailsComponent,
IconButtonModule,
CheckboxModule,
CommonModule,

View File

@ -0,0 +1,30 @@
<bit-section [formGroup]="sendFileDetailsForm">
<div *ngIf="config.mode === 'edit'">
<div class="tw-text-muted">{{ "file" | i18n }}</div>
<div>{{ originalSendView.file.fileName }}</div>
<div class="tw-text-muted">{{ originalSendView.file.sizeName }}</div>
</div>
<bit-form-field *ngIf="config.mode !== 'edit'">
<bit-label for="file">{{ "fileToShare" | i18n }}</bit-label>
<button bitButton type="button" buttonType="primary" (click)="fileSelector.click()">
{{ "chooseFile" | i18n }}
</button>
<span
class="tw-flex tw-items-center tw-pl-3"
[ngClass]="fileName ? 'tw-text-main' : 'tw-text-muted'"
>
{{ fileName || ("noFileChosen" | i18n) }}</span
>
<input
bitInput
#fileSelector
type="file"
formControlName="file"
hidden
(change)="onFileSelected($event)"
/>
<bit-hint>
{{ "maxFileSize" | i18n }}
</bit-hint>
</bit-form-field>
</bit-section>

View File

@ -0,0 +1,92 @@
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,
FormsModule,
} from "@angular/forms";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendFileView } from "@bitwarden/common/tools/send/models/view/send-file.view";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { ButtonModule, 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 BaseSendFileDetailsForm = FormGroup<{
file: FormControl<SendFileView | null>;
}>;
export type SendFileDetailsForm = BaseSendFileDetailsForm & BaseSendDetailsForm;
@Component({
selector: "tools-send-file-details",
templateUrl: "./send-file-details.component.html",
standalone: true,
imports: [
ButtonModule,
CommonModule,
JslibModule,
ReactiveFormsModule,
FormFieldModule,
SectionComponent,
FormsModule,
],
})
export class SendFileDetailsComponent implements OnInit {
@Input() config: SendFormConfig;
@Input() originalSendView?: SendView;
@Input() sendDetailsForm: BaseSendDetailsForm;
baseSendFileDetailsForm: BaseSendFileDetailsForm;
sendFileDetailsForm: SendFileDetailsForm;
FileSendType = SendType.File;
fileName = "";
constructor(
private formBuilder: FormBuilder,
protected sendFormContainer: SendFormContainer,
) {
this.baseSendFileDetailsForm = this.formBuilder.group({
file: this.formBuilder.control<SendFileView | null>(null, Validators.required),
});
this.sendFileDetailsForm = Object.assign(this.baseSendFileDetailsForm, this.sendDetailsForm);
this.sendFormContainer.registerChildForm("sendFileDetailsForm", this.sendFileDetailsForm);
this.sendFileDetailsForm.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.sendFormContainer.patchSend((send) => {
return Object.assign(send, {
file: value.file,
});
});
});
}
onFileSelected = (event: Event): void => {
const file = (event.target as HTMLInputElement).files?.[0];
if (!file) {
return;
}
this.fileName = file.name;
this.sendFormContainer.onFileSelected(file);
};
ngOnInit() {
if (this.originalSendView) {
this.sendFileDetailsForm.patchValue({
file: this.originalSendView.file,
});
}
}
}

View File

@ -65,6 +65,7 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send
private bitSubmit: BitSubmitDirective;
private destroyRef = inject(DestroyRef);
private _firstInitialized = false;
private file: File | null = null;
/**
* The form ID to use for the form. Used to connect it to a submit button.
@ -188,14 +189,17 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send
private i18nService: I18nService,
) {}
onFileSelected(file: File): void {
this.file = file;
}
submit = async () => {
if (this.sendForm.invalid) {
this.sendForm.markAllAsTouched();
return;
}
// TODO: Add file handling
await this.addEditFormService.saveSend(this.updatedSendView, null, this.config);
await this.addEditFormService.saveSend(this.updatedSendView, this.file, this.config);
this.toastService.showToast({
variant: "success",

View File

@ -2,6 +2,7 @@ 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 { SendFileDetailsForm } from "./components/send-details/send-file-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.
@ -10,6 +11,7 @@ import { SendTextDetailsForm } from "./components/send-details/send-text-details
export type SendForm = {
sendDetailsForm?: SendDetailsComponent["sendDetailsForm"];
sendTextDetailsForm?: SendTextDetailsForm;
sendFileDetailsForm?: SendFileDetailsForm;
};
/**
@ -37,5 +39,7 @@ export abstract class SendFormContainer {
group: Exclude<SendForm[K], undefined>,
): void;
abstract onFileSelected(file: File): void;
abstract patchSend(updateFn: (current: SendView) => SendView): void;
}