[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:
parent
beeb0354fd
commit
1320d96cb4
|
@ -6,6 +6,8 @@ const config: StorybookConfig = {
|
||||||
stories: [
|
stories: [
|
||||||
"../libs/auth/src/**/*.mdx",
|
"../libs/auth/src/**/*.mdx",
|
||||||
"../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)",
|
"../libs/auth/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||||
|
"../libs/tools/send/send-ui/src/**/*.mdx",
|
||||||
|
"../libs/tools/send/send-ui/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||||
"../libs/vault/src/**/*.mdx",
|
"../libs/vault/src/**/*.mdx",
|
||||||
"../libs/vault/src/**/*.stories.@(js|jsx|ts|tsx)",
|
"../libs/vault/src/**/*.stories.@(js|jsx|ts|tsx)",
|
||||||
"../libs/components/src/**/*.mdx",
|
"../libs/components/src/**/*.mdx",
|
||||||
|
|
|
@ -50,7 +50,7 @@ import { PasswordGeneratorHistoryComponent } from "../tools/popup/generator/pass
|
||||||
import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component";
|
import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component";
|
||||||
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
|
import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component";
|
||||||
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
|
import { SendTypeComponent } from "../tools/popup/send/send-type.component";
|
||||||
import { SendV2Component } from "../tools/popup/send/send-v2.component";
|
import { SendV2Component } from "../tools/popup/send-v2/send-v2.component";
|
||||||
import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-page-v2.component";
|
import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-page-v2.component";
|
||||||
import { AboutPageComponent } from "../tools/popup/settings/about-page/about-page.component";
|
import { AboutPageComponent } from "../tools/popup/settings/about-page/about-page.component";
|
||||||
import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component";
|
import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component";
|
||||||
|
|
|
@ -35,6 +35,7 @@ module.exports = {
|
||||||
"<rootDir>/libs/tools/generator/extensions/history/jest.config.js",
|
"<rootDir>/libs/tools/generator/extensions/history/jest.config.js",
|
||||||
"<rootDir>/libs/tools/generator/extensions/legacy/jest.config.js",
|
"<rootDir>/libs/tools/generator/extensions/legacy/jest.config.js",
|
||||||
"<rootDir>/libs/tools/generator/extensions/navigation/jest.config.js",
|
"<rootDir>/libs/tools/generator/extensions/navigation/jest.config.js",
|
||||||
|
"<rootDir>/libs/tools/send/send-ui/jest.config.js",
|
||||||
"<rootDir>/libs/importer/jest.config.js",
|
"<rootDir>/libs/importer/jest.config.js",
|
||||||
"<rootDir>/libs/platform/jest.config.js",
|
"<rootDir>/libs/platform/jest.config.js",
|
||||||
"<rootDir>/libs/node/jest.config.js",
|
"<rootDir>/libs/node/jest.config.js",
|
||||||
|
|
|
@ -8,4 +8,5 @@ export type CollectionId = Opaque<string, "CollectionId">;
|
||||||
export type ProviderId = Opaque<string, "ProviderId">;
|
export type ProviderId = Opaque<string, "ProviderId">;
|
||||||
export type PolicyId = Opaque<string, "PolicyId">;
|
export type PolicyId = Opaque<string, "PolicyId">;
|
||||||
export type CipherId = Opaque<string, "CipherId">;
|
export type CipherId = Opaque<string, "CipherId">;
|
||||||
|
export type SendId = Opaque<string, "SendId">;
|
||||||
export type IndexedEntityId = Opaque<string, "IndexedEntityId">;
|
export type IndexedEntityId = Opaque<string, "IndexedEntityId">;
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from "./icons";
|
export * from "./icons";
|
||||||
|
export * from "./send-form";
|
||||||
export { NewSendDropdownComponent } from "./new-send-dropdown/new-send-dropdown.component";
|
export { NewSendDropdownComponent } from "./new-send-dropdown/new-send-dropdown.component";
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
|
||||||
|
import { SendId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mode of the add/edit form.
|
||||||
|
* - `add` - The form is creating a new send.
|
||||||
|
* - `edit` - The form is editing an existing send.
|
||||||
|
* - `partial-edit` - The form is editing an existing send, but only the favorite/folder fields
|
||||||
|
*/
|
||||||
|
export type SendFormMode = "add" | "edit" | "partial-edit";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base configuration object for the send form. Includes all common fields.
|
||||||
|
*/
|
||||||
|
type BaseSendFormConfig = {
|
||||||
|
/**
|
||||||
|
* The mode of the form.
|
||||||
|
*/
|
||||||
|
mode: SendFormMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of send to create/edit.
|
||||||
|
*/
|
||||||
|
sendType: SendType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate if the user is allowed to create sends. If false, configuration must
|
||||||
|
* supply a list of organizations that the user can create sends in.
|
||||||
|
*/
|
||||||
|
areSendsAllowed: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The original send that is being edited or cloned. This can be undefined when creating a new send.
|
||||||
|
*/
|
||||||
|
originalSend?: Send;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for the send form when editing an existing send.
|
||||||
|
*/
|
||||||
|
type ExistingSendConfig = BaseSendFormConfig & {
|
||||||
|
mode: "edit" | "partial-edit";
|
||||||
|
originalSend: Send;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for the send form when creating a completely new send.
|
||||||
|
*/
|
||||||
|
type CreateNewSendConfig = BaseSendFormConfig & {
|
||||||
|
mode: "add";
|
||||||
|
};
|
||||||
|
|
||||||
|
type CombinedAddEditConfig = ExistingSendConfig | CreateNewSendConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for the send form when personal ownership is allowed.
|
||||||
|
*/
|
||||||
|
type SendsAllowed = CombinedAddEditConfig & {
|
||||||
|
areSendsAllowed: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for the send form when Sends are not allowed by an organization.
|
||||||
|
* Organizations must be provided.
|
||||||
|
*/
|
||||||
|
type SendsNotAllowed = CombinedAddEditConfig & {
|
||||||
|
areSendsAllowed: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration object for the send form.
|
||||||
|
* Determines the behavior of the form and the controls that are displayed/enabled.
|
||||||
|
*/
|
||||||
|
export type SendFormConfig = SendsAllowed | SendsNotAllowed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service responsible for building the configuration object for the send form.
|
||||||
|
*/
|
||||||
|
export abstract class SendFormConfigService {
|
||||||
|
/**
|
||||||
|
* Builds the configuration for the send form using the specified mode, sendId, and sendType.
|
||||||
|
* The other configuration fields will be fetched from their respective services.
|
||||||
|
* @param mode
|
||||||
|
* @param sendId
|
||||||
|
* @param sendType
|
||||||
|
*/
|
||||||
|
abstract buildConfig(
|
||||||
|
mode: SendFormMode,
|
||||||
|
sendId?: SendId,
|
||||||
|
sendType?: SendType,
|
||||||
|
): Promise<SendFormConfig>;
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
|
||||||
|
import { SendFormConfig } from "./send-form-config.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to save the send using the correct endpoint(s) and encapsulating the logic for decrypting the send.
|
||||||
|
*
|
||||||
|
* This service should only be used internally by the SendFormComponent.
|
||||||
|
*/
|
||||||
|
export abstract class SendFormService {
|
||||||
|
/**
|
||||||
|
* Helper to decrypt a send and avoid the need to call the send service directly.
|
||||||
|
* (useful for mocking tests/storybook).
|
||||||
|
*/
|
||||||
|
abstract decryptSend(send: Send): Promise<SendView>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the new or modified send with the server.
|
||||||
|
*/
|
||||||
|
abstract saveSend(
|
||||||
|
send: SendView,
|
||||||
|
file: File | ArrayBuffer,
|
||||||
|
config: SendFormConfig,
|
||||||
|
): Promise<SendView>;
|
||||||
|
}
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { SendFormModule } from "./send-form.module";
|
||||||
|
export {
|
||||||
|
SendFormConfigService,
|
||||||
|
SendFormConfig,
|
||||||
|
SendFormMode,
|
||||||
|
} from "./abstractions/send-form-config.service";
|
||||||
|
export { DefaultSendFormConfigService } from "./services/default-send-form-config.service";
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
|
||||||
|
import { SendFormConfig } from "./abstractions/send-form-config.service";
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container for the {@link SendForm} that allows for registration of child form groups and patching of the send
|
||||||
|
* to be updated/created. Child form components inject this container in order to register themselves with the parent form
|
||||||
|
* and access configuration options.
|
||||||
|
*
|
||||||
|
* This is an alternative to passing the form groups down through the component tree via @Inputs() and form updates via
|
||||||
|
* @Outputs(). It allows child forms to define their own structure and validation rules, while still being able to
|
||||||
|
* update the parent send.
|
||||||
|
*/
|
||||||
|
export abstract class SendFormContainer {
|
||||||
|
/**
|
||||||
|
* The configuration for the send form.
|
||||||
|
*/
|
||||||
|
readonly config: SendFormConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The original send that is being edited/cloned. Used to pre-populate the form and compare changes.
|
||||||
|
*/
|
||||||
|
readonly originalSendView: SendView | null;
|
||||||
|
|
||||||
|
abstract registerChildForm<K extends keyof SendForm>(
|
||||||
|
name: K,
|
||||||
|
group: Exclude<SendForm[K], undefined>,
|
||||||
|
): void;
|
||||||
|
|
||||||
|
abstract patchSend(send: Partial<SendView>): void;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Controls, Meta, Primary } from "@storybook/addon-docs";
|
||||||
|
|
||||||
|
import * as stories from "./send-form.stories";
|
||||||
|
|
||||||
|
<Meta of={stories} />
|
||||||
|
|
||||||
|
# Send Form
|
||||||
|
|
||||||
|
The send form is a re-usable form component that can be used to create, update, and clone sends. It
|
||||||
|
is configured via a `SendFormConfig` object that is passed to the component as a prop. The
|
||||||
|
`SendFormConfig` object can be created manually, or a `SendFormConfigService` can be used to create
|
||||||
|
it. A default implementation of the `SendFormConfigService` exists in the `@bitwarden/send-ui`
|
||||||
|
library.
|
||||||
|
|
||||||
|
<Primary />
|
||||||
|
|
||||||
|
<Controls include={["config", "submitBtn"]} />
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { NgModule } from "@angular/core";
|
||||||
|
|
||||||
|
import { SendFormService } from "./abstractions/send-form.service";
|
||||||
|
import { SendFormComponent } from "./components/send-form.component";
|
||||||
|
import { DefaultSendFormService } from "./services/default-send-form.service";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [SendFormComponent],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SendFormService,
|
||||||
|
useClass: DefaultSendFormService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
exports: [SendFormComponent],
|
||||||
|
})
|
||||||
|
export class SendFormModule {}
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { importProvidersFrom } from "@angular/core";
|
||||||
|
import { action } from "@storybook/addon-actions";
|
||||||
|
import {
|
||||||
|
applicationConfig,
|
||||||
|
componentWrapperDecorator,
|
||||||
|
Meta,
|
||||||
|
moduleMetadata,
|
||||||
|
StoryObj,
|
||||||
|
} from "@storybook/angular";
|
||||||
|
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
import { AsyncActionsModule, ButtonModule, ToastService } from "@bitwarden/components";
|
||||||
|
import { SendFormConfig } from "@bitwarden/send-ui";
|
||||||
|
import { PreloadedEnglishI18nModule } from "@bitwarden/web-vault/src/app/core/tests";
|
||||||
|
|
||||||
|
import { SendFormService } from "./abstractions/send-form.service";
|
||||||
|
import { SendFormComponent } from "./components/send-form.component";
|
||||||
|
import { SendFormModule } from "./send-form.module";
|
||||||
|
|
||||||
|
const defaultConfig: SendFormConfig = {
|
||||||
|
mode: "add",
|
||||||
|
sendType: SendType.Text,
|
||||||
|
areSendsAllowed: true,
|
||||||
|
originalSend: {
|
||||||
|
id: "123",
|
||||||
|
name: "Test Send",
|
||||||
|
notes: "Example notes",
|
||||||
|
} as unknown as Send,
|
||||||
|
};
|
||||||
|
|
||||||
|
class TestAddEditFormService implements SendFormService {
|
||||||
|
decryptSend(): Promise<SendView> {
|
||||||
|
return Promise.resolve(defaultConfig.originalSend as any);
|
||||||
|
}
|
||||||
|
async saveSend(send: SendView, file: File | ArrayBuffer): Promise<SendView> {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
return send;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionsData = {
|
||||||
|
onSave: action("onSave"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Tools/Send Form",
|
||||||
|
component: SendFormComponent,
|
||||||
|
decorators: [
|
||||||
|
moduleMetadata({
|
||||||
|
imports: [SendFormModule, AsyncActionsModule, ButtonModule],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SendFormService,
|
||||||
|
useClass: TestAddEditFormService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ToastService,
|
||||||
|
useValue: {
|
||||||
|
showToast: action("showToast"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
componentWrapperDecorator(
|
||||||
|
(story) => `<div class="tw-bg-background-alt tw-text-main tw-border">${story}</div>`,
|
||||||
|
),
|
||||||
|
applicationConfig({
|
||||||
|
providers: [importProvidersFrom(PreloadedEnglishI18nModule)],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
config: defaultConfig,
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
config: {
|
||||||
|
description: "The configuration object for the form.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<SendFormComponent>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
onSave: actionsData.onSave,
|
||||||
|
...args,
|
||||||
|
},
|
||||||
|
template: /*html*/ `
|
||||||
|
<tools-send-form [config]="config" (cipherSaved)="onSave($event)" formId="test-form" [submitBtn]="submitBtn"></tools-send-form>
|
||||||
|
<button type="submit" form="test-form" bitButton buttonType="primary" #submitBtn>Submit</button>
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Edit: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
config: {
|
||||||
|
...defaultConfig,
|
||||||
|
mode: "edit",
|
||||||
|
originalSend: defaultConfig.originalSend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PartialEdit: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
config: {
|
||||||
|
...defaultConfig,
|
||||||
|
mode: "partial-edit",
|
||||||
|
originalSend: defaultConfig.originalSend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SendsHaveBeenDisabledByPolicy: Story = {
|
||||||
|
...Default,
|
||||||
|
args: {
|
||||||
|
config: {
|
||||||
|
...defaultConfig,
|
||||||
|
mode: "add",
|
||||||
|
areSendsAllowed: false,
|
||||||
|
originalSend: defaultConfig.originalSend,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
import { combineLatest, firstValueFrom, map } from "rxjs";
|
||||||
|
|
||||||
|
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||||
|
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||||
|
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||||
|
import { SendId } from "@bitwarden/common/types/guid";
|
||||||
|
|
||||||
|
import {
|
||||||
|
SendFormConfig,
|
||||||
|
SendFormConfigService,
|
||||||
|
SendFormMode,
|
||||||
|
} from "../abstractions/send-form-config.service";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of the `SendFormConfigService`.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class DefaultSendFormConfigService implements SendFormConfigService {
|
||||||
|
private policyService: PolicyService = inject(PolicyService);
|
||||||
|
private sendService: SendService = inject(SendService);
|
||||||
|
|
||||||
|
async buildConfig(
|
||||||
|
mode: SendFormMode,
|
||||||
|
sendId?: SendId,
|
||||||
|
sendType?: SendType,
|
||||||
|
): Promise<SendFormConfig> {
|
||||||
|
const [areSendsAllowed, send] = await firstValueFrom(
|
||||||
|
combineLatest([this.areSendsEnabled$, this.getSend(sendId)]),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
sendType: sendType,
|
||||||
|
areSendsAllowed,
|
||||||
|
originalSend: send,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private areSendsEnabled$ = this.policyService
|
||||||
|
.policyAppliesToActiveUser$(PolicyType.DisableSend)
|
||||||
|
.pipe(map((p) => !p));
|
||||||
|
|
||||||
|
private getSend(id?: SendId) {
|
||||||
|
if (id == null) {
|
||||||
|
return Promise.resolve(null);
|
||||||
|
}
|
||||||
|
return this.sendService.get$(id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { inject, Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Send } from "@bitwarden/common/tools/send/models/domain/send";
|
||||||
|
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||||
|
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||||
|
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||||
|
|
||||||
|
import { SendFormConfig } from "../abstractions/send-form-config.service";
|
||||||
|
import { SendFormService } from "../abstractions/send-form.service";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DefaultSendFormService implements SendFormService {
|
||||||
|
private sendApiService: SendApiService = inject(SendApiService);
|
||||||
|
private sendService = inject(SendService);
|
||||||
|
|
||||||
|
async decryptSend(send: Send): Promise<SendView> {
|
||||||
|
return await send.decrypt();
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSend(
|
||||||
|
send: SendView,
|
||||||
|
file: File | ArrayBuffer,
|
||||||
|
config: SendFormConfig,
|
||||||
|
): Promise<SendView> {
|
||||||
|
const sendData = await this.sendService.encrypt(send, file, send.password, null);
|
||||||
|
const savedSend = await this.sendApiService.save(sendData);
|
||||||
|
return await savedSend.decrypt();
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ config.content = [
|
||||||
"./libs/auth/src/**/*.{html,ts,mdx}",
|
"./libs/auth/src/**/*.{html,ts,mdx}",
|
||||||
"./libs/billing/src/**/*.{html,ts,mdx}",
|
"./libs/billing/src/**/*.{html,ts,mdx}",
|
||||||
"./libs/platform/src/**/*.{html,ts,mdx}",
|
"./libs/platform/src/**/*.{html,ts,mdx}",
|
||||||
|
"./libs/tools/send/send-ui/src/*.{html,ts,mdx}",
|
||||||
"./libs/vault/src/**/*.{html,ts,mdx}",
|
"./libs/vault/src/**/*.{html,ts,mdx}",
|
||||||
"./apps/web/src/**/*.{html,ts,mdx}",
|
"./apps/web/src/**/*.{html,ts,mdx}",
|
||||||
"./bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
|
"./bitwarden_license/bit-web/src/**/*.{html,ts,mdx}",
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"apps/web/src/**/*",
|
"apps/web/src/**/*",
|
||||||
"apps/browser/src/**/*",
|
"apps/browser/src/**/*",
|
||||||
"libs/*/src/**/*",
|
"libs/*/src/**/*",
|
||||||
|
"libs/tools/send/**/src/**/*",
|
||||||
"bitwarden_license/bit-web/src/**/*",
|
"bitwarden_license/bit-web/src/**/*",
|
||||||
"bitwarden_license/bit-common/src/**/*"
|
"bitwarden_license/bit-common/src/**/*"
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue