mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[PM-12681] Enable new Send Add/Edit Dialog on web (#13361)
* Create web-specific new-send-dropdown component * Create web-specifc Send Add/Edit dialog * Use new-send-dropdown and replace old Send Add/Edit with new Add/Edit dialog * Delete old Send Add/Edit component * Remove unused entries from en/messages.json * Add cancel button to close dialog * Remove unused RouterLink * Fix typechecking issue * Use observable to show/hide premium badge * Add documentation * Move assignment of observable into ctor, as it no longer requires a promise for assignment --------- Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0d68d22b98
commit
bfbad99fb7
@@ -67,7 +67,6 @@ import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } f
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../tools/reports/pages/organizations/unsecured-websites-report.component";
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../tools/reports/pages/organizations/weak-passwords-report.component";
|
||||
/* eslint no-restricted-imports: "error" */
|
||||
import { AddEditComponent as SendAddEditComponent } from "../tools/send/add-edit.component";
|
||||
import { PremiumBadgeComponent } from "../vault/components/premium-badge.component";
|
||||
import { AddEditCustomFieldsComponent } from "../vault/individual-vault/add-edit-custom-fields.component";
|
||||
import { AddEditComponent } from "../vault/individual-vault/add-edit.component";
|
||||
@@ -148,7 +147,6 @@ import { SharedModule } from "./shared.module";
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
@@ -212,7 +210,6 @@ import { SharedModule } from "./shared.module";
|
||||
SecurityComponent,
|
||||
SecurityKeysComponent,
|
||||
SelectableAvatarComponent,
|
||||
SendAddEditComponent,
|
||||
SetPasswordComponent,
|
||||
SponsoredFamiliesComponent,
|
||||
SponsoringOrgRowComponent,
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
<form
|
||||
[formGroup]="formGroup"
|
||||
[bitSubmit]="submitAndClose"
|
||||
[appApiAction]="formPromise"
|
||||
autocomplete="off"
|
||||
>
|
||||
<bit-dialog dialogSize="large">
|
||||
<span bitDialogTitle>
|
||||
{{ title }}
|
||||
</span>
|
||||
<span bitDialogContent *ngIf="send">
|
||||
<bit-callout *ngIf="disableSend">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
<bit-callout *ngIf="!disableSend && disableHideEmail">
|
||||
{{ "sendOptionsPolicyInEffect" | i18n }}
|
||||
<ul class="tw-mb-0">
|
||||
<li>{{ "sendDisableHideEmailInEffect" | i18n }}</li>
|
||||
</ul>
|
||||
</bit-callout>
|
||||
<bit-form-field class="tw-w-1/2">
|
||||
<bit-label for="name">{{ "name" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="name" />
|
||||
<bit-hint>{{ "sendNameDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<div class="tw-flex" *ngIf="!editMode">
|
||||
<bit-radio-group formControlName="type">
|
||||
<bit-label>{{ "whatTypeOfSend" | i18n }}</bit-label>
|
||||
|
||||
<bit-radio-button
|
||||
*ngFor="let o of typeOptions"
|
||||
id="type_{{ o.value }}"
|
||||
class="tw-block"
|
||||
[value]="o.value"
|
||||
[disabled]="!canAccessPremium && o.premium"
|
||||
>
|
||||
<bit-label>
|
||||
{{ o.name }}
|
||||
<app-premium-badge
|
||||
class="tw-mx-1"
|
||||
*ngIf="!canAccessPremium && o.premium"
|
||||
slot="end"
|
||||
></app-premium-badge>
|
||||
</bit-label>
|
||||
</bit-radio-button>
|
||||
</bit-radio-group>
|
||||
</div>
|
||||
<!-- Text -->
|
||||
<ng-container *ngIf="type === sendType.Text">
|
||||
<bit-form-field>
|
||||
<bit-label for="text">{{ "sendTypeText" | i18n }}</bit-label>
|
||||
<textarea bitInput id="text" rows="6" formControlName="text"></textarea>
|
||||
<bit-hint>{{ "sendTextDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="textHidden" />
|
||||
<bit-label>{{ "textHiddenByDefault" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</ng-container>
|
||||
<!-- File -->
|
||||
<ng-container *ngIf="type === sendType.File">
|
||||
<div class="tw-flex">
|
||||
<div *ngIf="editMode">
|
||||
<bit-label>{{ "file" | i18n }}</bit-label>
|
||||
<p bitTypography="body1" class="tw-mb-0">
|
||||
{{ send.file.fileName }} ({{ send.file.sizeName }})
|
||||
</p>
|
||||
</div>
|
||||
<bit-form-field *ngIf="!editMode">
|
||||
<bit-label>{{ "file" | i18n }}</bit-label>
|
||||
<div>
|
||||
<button bitButton type="button" buttonType="secondary" (click)="fileSelector.click()">
|
||||
{{ "chooseFile" | i18n }}
|
||||
</button>
|
||||
{{ selectedFile?.name ?? ("noFileChosen" | i18n) }}
|
||||
</div>
|
||||
<input
|
||||
bitInput
|
||||
#fileSelector
|
||||
type="file"
|
||||
id="file"
|
||||
name="file"
|
||||
formControlName="file"
|
||||
(change)="setSelectedFile($event)"
|
||||
hidden
|
||||
class="tw-hidden"
|
||||
/>
|
||||
<bit-hint>{{ "sendFileDesc" | i18n }} {{ "maxFileSize" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</ng-container>
|
||||
<h4 bitTypography="h4" class="tw-mt-5">{{ "share" | i18n }}</h4>
|
||||
|
||||
<bit-form-field *ngIf="link">
|
||||
<bit-label for="link">{{ "sendLinkLabel" | i18n }}</bit-label>
|
||||
<input bitInput type="text" readonly formControlName="link" />
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="copyLink" />
|
||||
<bit-label>{{ "copySendLinkOnSave" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
<div class="tw-mt-5 tw-flex" (click)="toggleOptions()">
|
||||
<h4 bitTypography="h4" class="tw-mb-0 tw-mr-2">
|
||||
<button type="button" bitLink appStopClick [attr.aria-expanded]="showOptions">
|
||||
<i
|
||||
class="bwi"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-angle-right': !showOptions, 'bwi-angle-down': showOptions }"
|
||||
></i>
|
||||
{{ "options" | i18n }}
|
||||
</button>
|
||||
</h4>
|
||||
</div>
|
||||
<div id="options" [hidden]="!showOptions">
|
||||
<div class="tw-flex">
|
||||
<div *ngIf="!editMode" class="tw-w-1/2 tw-pr-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
|
||||
<bit-select
|
||||
id="deletionDate"
|
||||
name="SelectedDeletionDatePreset"
|
||||
formControlName="selectedDeletionDatePreset"
|
||||
>
|
||||
<bit-option
|
||||
*ngFor="let o of deletionDatePresets"
|
||||
[value]="o.value"
|
||||
[label]="o.name"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
<ng-container *ngIf="formGroup.controls['selectedDeletionDatePreset'].value === 0">
|
||||
<input
|
||||
bitInput
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</ng-container>
|
||||
<bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="editMode" class="tw-w-1/2 tw-pr-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="deletionDate">{{ "deletionDate" | i18n }}</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
id="deletionDateCustom"
|
||||
type="datetime-local"
|
||||
name="DeletionDate"
|
||||
formControlName="defaultDeletionDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<bit-hint>{{ "deletionDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="!editMode" class="tw-w-1/2 tw-pl-3">
|
||||
<bit-form-field>
|
||||
<bit-label for="expirationDate">
|
||||
{{ "expirationDate" | i18n }}
|
||||
</bit-label>
|
||||
<bit-select
|
||||
bitInput
|
||||
id="expirationDate"
|
||||
name="SelectedExpirationDatePreset"
|
||||
formControlName="selectedExpirationDatePreset"
|
||||
>
|
||||
<bit-option
|
||||
*ngFor="let e of expirationDatePresets"
|
||||
[value]="e.value"
|
||||
[label]="e.name"
|
||||
></bit-option>
|
||||
</bit-select>
|
||||
<ng-container *ngIf="formGroup.controls['selectedExpirationDatePreset'].value === 0">
|
||||
<input
|
||||
bitInput
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
</ng-container>
|
||||
<bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div *ngIf="editMode" class="tw-w-1/2 tw-pl-3">
|
||||
<bit-form-field>
|
||||
<bit-label class="tw-flex" for="expirationDate">
|
||||
{{ "expirationDate" | i18n }}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
bitLink
|
||||
appStopClick
|
||||
(click)="clearExpiration()"
|
||||
*ngIf="!disableSend"
|
||||
class="tw-ml-auto"
|
||||
slot="end"
|
||||
>
|
||||
{{ "clear" | i18n }}
|
||||
</button>
|
||||
</bit-label>
|
||||
<input
|
||||
bitInput
|
||||
id="expirationDateCustom"
|
||||
type="datetime-local"
|
||||
name="ExpirationDate"
|
||||
formControlName="defaultExpirationDateTime"
|
||||
placeholder="MM/DD/YYYY HH:MM AM/PM"
|
||||
/>
|
||||
<bit-hint>{{ "expirationDateDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-w-1/2 tw-pr-3">
|
||||
<bit-label for="maxAccessCount">{{ "maxAccessCount" | i18n }}</bit-label>
|
||||
<input bitInput type="number" formControlName="maxAccessCount" min="1" />
|
||||
<bit-hint>{{ "maxAccessCountDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-field class="tw-w-1/2 tw-pl-3" *ngIf="editMode">
|
||||
<bit-label for="accessCount">{{ "currentAccessCount" | i18n }}</bit-label>
|
||||
<input bitInput type="text" formControlName="accessCount" readonly />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<div class="tw-flex">
|
||||
<bit-form-field class="tw-w-1/2 tw-pr-3">
|
||||
<bit-label for="password" *ngIf="!hasPassword">{{ "password" | i18n }}</bit-label>
|
||||
<bit-label for="password" *ngIf="hasPassword">{{ "newPassword" | i18n }}</bit-label>
|
||||
|
||||
<input bitInput type="password" formControlName="password" />
|
||||
<button type="button" bitIconButton bitSuffix bitPasswordInputToggle></button>
|
||||
<bit-hint>{{ "sendPasswordDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "notes" | i18n }}</bit-label>
|
||||
<textarea bitInput formControlName="notes" rows="6"></textarea>
|
||||
<bit-hint>{{ "sendNotesDesc" | i18n }}</bit-hint>
|
||||
</bit-form-field>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="hideEmail" />
|
||||
<bit-label>{{ "hideEmail" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
|
||||
<bit-form-control>
|
||||
<input bitCheckbox type="checkbox" formControlName="disabled" />
|
||||
<bit-label>{{ "disableThisSend" | i18n }}</bit-label>
|
||||
</bit-form-control>
|
||||
</div>
|
||||
</span>
|
||||
<ng-container bitDialogFooter>
|
||||
<button
|
||||
type="submit"
|
||||
bitButton
|
||||
bitFormButton
|
||||
[appA11yTitle]="'save' | i18n"
|
||||
buttonType="primary"
|
||||
>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
bitButton
|
||||
buttonType="secondary"
|
||||
[appA11yTitle]="'cancel' | i18n"
|
||||
bitDialogClose
|
||||
>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
*ngIf="editMode"
|
||||
type="button"
|
||||
class="tw-ml-auto"
|
||||
bitIconButton="bwi-trash"
|
||||
buttonType="danger"
|
||||
[appA11yTitle]="'delete' | i18n"
|
||||
[bitAction]="deleteAndClose"
|
||||
></button>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
</form>
|
||||
@@ -1,102 +0,0 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { AddEditComponent as BaseAddEditComponent } from "@bitwarden/angular/tools/send/add-edit.component";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
|
||||
import { DialogService, ToastService } from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
selector: "app-send-add-edit",
|
||||
templateUrl: "add-edit.component.html",
|
||||
})
|
||||
export class AddEditComponent extends BaseAddEditComponent {
|
||||
override componentName = "app-send-add-edit";
|
||||
protected selectedFile: File;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
datePipe: DatePipe,
|
||||
sendService: SendService,
|
||||
stateService: StateService,
|
||||
messagingService: MessagingService,
|
||||
policyService: PolicyService,
|
||||
logService: LogService,
|
||||
sendApiService: SendApiService,
|
||||
dialogService: DialogService,
|
||||
formBuilder: FormBuilder,
|
||||
billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
protected dialogRef: DialogRef,
|
||||
@Inject(DIALOG_DATA) params: { sendId: string },
|
||||
accountService: AccountService,
|
||||
toastService: ToastService,
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
platformUtilsService,
|
||||
environmentService,
|
||||
datePipe,
|
||||
sendService,
|
||||
messagingService,
|
||||
policyService,
|
||||
logService,
|
||||
stateService,
|
||||
sendApiService,
|
||||
dialogService,
|
||||
formBuilder,
|
||||
billingAccountProfileStateService,
|
||||
accountService,
|
||||
toastService,
|
||||
);
|
||||
|
||||
this.sendId = params.sendId;
|
||||
}
|
||||
|
||||
async copyLinkToClipboard(link: string): Promise<void | boolean> {
|
||||
// Copy function on web depends on the modal being open or not. Since this event occurs during a transition
|
||||
// of the modal closing we need to add a small delay to make sure state of the DOM is consistent.
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => resolve(super.copyLinkToClipboard(link)), 500);
|
||||
});
|
||||
}
|
||||
|
||||
protected setSelectedFile(event: Event) {
|
||||
const fileInputEl = <HTMLInputElement>event.target;
|
||||
const file = fileInputEl.files.length > 0 ? fileInputEl.files[0] : null;
|
||||
this.selectedFile = file;
|
||||
}
|
||||
|
||||
submitAndClose = async () => {
|
||||
this.formGroup.markAllAsTouched();
|
||||
if (this.formGroup.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await this.submit();
|
||||
if (success) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
};
|
||||
|
||||
deleteAndClose = async () => {
|
||||
const success = await this.delete();
|
||||
if (success) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<button bitButton [bitMenuTriggerFor]="itemOptions" buttonType="primary" type="button">
|
||||
<i *ngIf="!hideIcon" class="bwi bwi-plus-f" aria-hidden="true"></i>
|
||||
{{ (hideIcon ? "createSend" : "new") | i18n }}
|
||||
</button>
|
||||
<bit-menu #itemOptions>
|
||||
<a bitMenuItem (click)="createSend(sendType.Text)">
|
||||
<i class="bwi bwi-file-text" slot="start" aria-hidden="true"></i>
|
||||
{{ "sendTypeText" | i18n }}
|
||||
</a>
|
||||
<a bitMenuItem (click)="createSend(sendType.File)">
|
||||
<i class="bwi bwi-file" slot="start" aria-hidden="true"></i>
|
||||
{{ "sendTypeFile" | i18n }}
|
||||
<button
|
||||
type="button"
|
||||
slot="end"
|
||||
*ngIf="!(canAccessPremium$ | async)"
|
||||
bitBadge
|
||||
variant="success"
|
||||
>
|
||||
{{ "premium" | i18n }}
|
||||
</button>
|
||||
</a>
|
||||
</bit-menu>
|
||||
@@ -0,0 +1,65 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { firstValueFrom, Observable, of, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { BadgeModule, ButtonModule, DialogService, MenuModule } from "@bitwarden/components";
|
||||
import { DefaultSendFormConfigService } from "@bitwarden/send-ui";
|
||||
|
||||
import { SendAddEditComponent } from "../send-add-edit.component";
|
||||
|
||||
@Component({
|
||||
selector: "tools-new-send-dropdown",
|
||||
templateUrl: "new-send-dropdown.component.html",
|
||||
standalone: true,
|
||||
imports: [JslibModule, CommonModule, ButtonModule, MenuModule, BadgeModule],
|
||||
providers: [DefaultSendFormConfigService],
|
||||
})
|
||||
/**
|
||||
* A dropdown component that allows the user to create a new Send of a specific type.
|
||||
*/
|
||||
export class NewSendDropdownComponent {
|
||||
/** If true, the plus icon will be hidden */
|
||||
@Input() hideIcon: boolean = false;
|
||||
|
||||
/** SendType provided for the markup to pass back the selected type of Send */
|
||||
protected sendType = SendType;
|
||||
|
||||
/** Indicates whether the user can access premium features. */
|
||||
protected canAccessPremium$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private accountService: AccountService,
|
||||
private dialogService: DialogService,
|
||||
private addEditFormConfigService: DefaultSendFormConfigService,
|
||||
) {
|
||||
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
account
|
||||
? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id)
|
||||
: of(false),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the SendAddEditComponent for a new Send with the provided type.
|
||||
* If has user does not have premium access and the type is File, the user will be redirected to the premium settings page.
|
||||
* @param type The type of Send to create.
|
||||
*/
|
||||
async createSend(type: SendType) {
|
||||
if (!(await firstValueFrom(this.canAccessPremium$)) && type === SendType.File) {
|
||||
return await this.router.navigate(["settings/subscription/premium"]);
|
||||
}
|
||||
|
||||
const formConfig = await this.addEditFormConfigService.buildConfig("add", undefined, type);
|
||||
|
||||
await SendAddEditComponent.open(this.dialogService, { formConfig });
|
||||
}
|
||||
}
|
||||
35
apps/web/src/app/tools/send/send-add-edit.component.html
Normal file
35
apps/web/src/app/tools/send/send-add-edit.component.html
Normal file
@@ -0,0 +1,35 @@
|
||||
<bit-dialog #dialog dialogSize="large" background="alt">
|
||||
<span bitDialogTitle>
|
||||
{{ headerText }}
|
||||
</span>
|
||||
<span bitDialogContent>
|
||||
<tools-send-form
|
||||
formId="sendForm"
|
||||
[config]="config"
|
||||
(onSendCreated)="onSendCreated($event)"
|
||||
(onSendUpdated)="onSendUpdated($event)"
|
||||
[submitBtn]="submitBtn"
|
||||
>
|
||||
</tools-send-form>
|
||||
</span>
|
||||
|
||||
<ng-container bitDialogFooter>
|
||||
<button bitButton type="submit" form="sendForm" buttonType="primary" #submitBtn>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton type="button" buttonType="secondary" bitDialogClose>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<div class="tw-ml-auto">
|
||||
<button
|
||||
*ngIf="config?.mode !== 'add'"
|
||||
type="button"
|
||||
buttonType="danger"
|
||||
slot="end"
|
||||
bitIconButton="bwi-trash"
|
||||
[bitAction]="deleteSend"
|
||||
appA11yTitle="{{ 'delete' | i18n }}"
|
||||
></button>
|
||||
</div>
|
||||
</ng-container>
|
||||
</bit-dialog>
|
||||
176
apps/web/src/app/tools/send/send-add-edit.component.ts
Normal file
176
apps/web/src/app/tools/send/send-add-edit.component.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { DIALOG_DATA, DialogRef } from "@angular/cdk/dialog";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, Inject } from "@angular/core";
|
||||
import { FormsModule } 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 { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
|
||||
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
|
||||
import {
|
||||
AsyncActionsModule,
|
||||
ButtonModule,
|
||||
DialogService,
|
||||
IconButtonModule,
|
||||
SearchModule,
|
||||
ToastService,
|
||||
DialogModule,
|
||||
} from "@bitwarden/components";
|
||||
import { SendFormConfig, SendFormMode, SendFormModule } from "@bitwarden/send-ui";
|
||||
|
||||
export interface SendItemDialogParams {
|
||||
/**
|
||||
* The configuration object for the dialog and form.
|
||||
*/
|
||||
formConfig: SendFormConfig;
|
||||
|
||||
/**
|
||||
* If true, the "edit" button will be disabled in the dialog.
|
||||
*/
|
||||
disableForm?: boolean;
|
||||
}
|
||||
|
||||
export enum SendItemDialogResult {
|
||||
/**
|
||||
* A Send was saved (created or updated).
|
||||
*/
|
||||
Saved = "saved",
|
||||
|
||||
/**
|
||||
* A Send was deleted.
|
||||
*/
|
||||
Deleted = "deleted",
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for adding or editing a send item.
|
||||
*/
|
||||
@Component({
|
||||
selector: "tools-send-add-edit",
|
||||
templateUrl: "send-add-edit.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
SearchModule,
|
||||
JslibModule,
|
||||
FormsModule,
|
||||
ButtonModule,
|
||||
IconButtonModule,
|
||||
SendFormModule,
|
||||
AsyncActionsModule,
|
||||
DialogModule,
|
||||
],
|
||||
})
|
||||
export class SendAddEditComponent {
|
||||
/**
|
||||
* The header text for the component.
|
||||
*/
|
||||
headerText: string;
|
||||
|
||||
/**
|
||||
* The configuration for the send form.
|
||||
*/
|
||||
config: SendFormConfig;
|
||||
|
||||
constructor(
|
||||
@Inject(DIALOG_DATA) protected params: SendItemDialogParams,
|
||||
private dialogRef: DialogRef<SendItemDialogResult>,
|
||||
private i18nService: I18nService,
|
||||
private sendApiService: SendApiService,
|
||||
private toastService: ToastService,
|
||||
private dialogService: DialogService,
|
||||
) {
|
||||
this.config = params.formConfig;
|
||||
this.headerText = this.getHeaderText(this.config.mode, this.config.sendType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the send is created.
|
||||
*/
|
||||
async onSendCreated(send: SendView) {
|
||||
// FIXME Add dialogService.open send-created dialog
|
||||
this.dialogRef.close(SendItemDialogResult.Saved);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the send is updated.
|
||||
*/
|
||||
async onSendUpdated(send: SendView) {
|
||||
this.dialogRef.close(SendItemDialogResult.Saved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the event when the send is deleted.
|
||||
*/
|
||||
async onSendDeleted() {
|
||||
this.dialogRef.close(SendItemDialogResult.Deleted);
|
||||
|
||||
this.toastService.showToast({
|
||||
variant: "success",
|
||||
title: null,
|
||||
message: this.i18nService.t("deletedSend"),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the deletion of the current Send.
|
||||
*/
|
||||
deleteSend = async () => {
|
||||
const confirmed = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "deleteSend" },
|
||||
content: { key: "deleteSendPermanentConfirmation" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sendApiService.delete(this.config.originalSend?.id);
|
||||
} catch (e) {
|
||||
this.toastService.showToast({
|
||||
variant: "error",
|
||||
title: null,
|
||||
message: e.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await this.onSendDeleted();
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the header text based on the mode and type.
|
||||
* @param mode The mode of the send form.
|
||||
* @param type The type of the send
|
||||
* @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("textSend"));
|
||||
case SendType.File:
|
||||
return this.i18nService.t(headerKey, this.i18nService.t("fileSend"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the send add/edit dialog.
|
||||
* @param dialogService Instance of the DialogService.
|
||||
* @param params The parameters for the dialog.
|
||||
* @returns The dialog result.
|
||||
*/
|
||||
static open(dialogService: DialogService, params: SendItemDialogParams) {
|
||||
return dialogService.open<SendItemDialogResult, SendItemDialogParams>(SendAddEditComponent, {
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,7 @@
|
||||
</ng-container>
|
||||
</small>
|
||||
</ng-container>
|
||||
|
||||
<button type="button" bitButton buttonType="primary" (click)="addSend()" [disabled]="disableSend">
|
||||
<i class="bwi bwi-plus bwi-fw" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
<tools-new-send-dropdown *ngIf="!disableSend"></tools-new-send-dropdown>
|
||||
</app-header>
|
||||
|
||||
<bit-callout type="warning" title="{{ 'sendDisabled' | i18n }}" *ngIf="disableSend">
|
||||
@@ -198,10 +194,11 @@
|
||||
<bit-no-items [icon]="noItemIcon" class="tw-text-main">
|
||||
<ng-container slot="title">{{ "sendsNoItemsTitle" | i18n }}</ng-container>
|
||||
<ng-container slot="description">{{ "sendsNoItemsMessage" | i18n }}</ng-container>
|
||||
<button slot="button" type="button" bitButton buttonType="secondary" (click)="addSend()">
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i>
|
||||
{{ "createSend" | i18n }}
|
||||
</button>
|
||||
<tools-new-send-dropdown
|
||||
[hideIcon]="true"
|
||||
*ngIf="!disableSend"
|
||||
slot="button"
|
||||
></tools-new-send-dropdown>
|
||||
</bit-no-items>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import { Component, NgZone, ViewChild, OnInit, OnDestroy, ViewContainerRef } from "@angular/core";
|
||||
import { DialogRef } from "@angular/cdk/dialog";
|
||||
import { Component, NgZone, OnInit, OnDestroy } from "@angular/core";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
|
||||
import { SendComponent as BaseSendComponent } from "@bitwarden/angular/tools/send/send.component";
|
||||
@@ -14,6 +15,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
||||
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 { SendId } from "@bitwarden/common/types/guid";
|
||||
import {
|
||||
DialogService,
|
||||
NoItemsModule,
|
||||
@@ -21,24 +23,25 @@ import {
|
||||
TableDataSource,
|
||||
ToastService,
|
||||
} from "@bitwarden/components";
|
||||
import { NoSendsIcon } from "@bitwarden/send-ui";
|
||||
import { DefaultSendFormConfigService, NoSendsIcon, SendFormConfig } from "@bitwarden/send-ui";
|
||||
|
||||
import { HeaderModule } from "../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../shared";
|
||||
|
||||
import { AddEditComponent } from "./add-edit.component";
|
||||
import { NewSendDropdownComponent } from "./new-send/new-send-dropdown.component";
|
||||
import { SendAddEditComponent, SendItemDialogResult } from "./send-add-edit.component";
|
||||
|
||||
const BroadcasterSubscriptionId = "SendComponent";
|
||||
|
||||
@Component({
|
||||
selector: "app-send",
|
||||
standalone: true,
|
||||
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule],
|
||||
imports: [SharedModule, SearchModule, NoItemsModule, HeaderModule, NewSendDropdownComponent],
|
||||
templateUrl: "send.component.html",
|
||||
providers: [DefaultSendFormConfigService],
|
||||
})
|
||||
export class SendComponent extends BaseSendComponent implements OnInit, OnDestroy {
|
||||
@ViewChild("sendAddEdit", { read: ViewContainerRef, static: true })
|
||||
sendAddEditModalRef: ViewContainerRef;
|
||||
private sendItemDialogRef?: DialogRef<SendItemDialogResult> | undefined;
|
||||
noItemIcon = NoSendsIcon;
|
||||
|
||||
override set filteredSends(filteredSends: SendView[]) {
|
||||
@@ -65,6 +68,7 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
sendApiService: SendApiService,
|
||||
dialogService: DialogService,
|
||||
toastService: ToastService,
|
||||
private addEditFormConfigService: DefaultSendFormConfigService,
|
||||
) {
|
||||
super(
|
||||
sendService,
|
||||
@@ -111,17 +115,41 @@ export class SendComponent extends BaseSendComponent implements OnInit, OnDestro
|
||||
return;
|
||||
}
|
||||
|
||||
await this.editSend(null);
|
||||
const config = await this.addEditFormConfigService.buildConfig("add", null, 0);
|
||||
|
||||
await this.openSendItemDialog(config);
|
||||
}
|
||||
|
||||
async editSend(send: SendView) {
|
||||
const dialog = this.dialogService.open(AddEditComponent, {
|
||||
data: {
|
||||
sendId: send == null ? null : send.id,
|
||||
},
|
||||
const config = await this.addEditFormConfigService.buildConfig(
|
||||
send == null ? "add" : "edit",
|
||||
send == null ? null : (send.id as SendId),
|
||||
send.type,
|
||||
);
|
||||
|
||||
await this.openSendItemDialog(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the send item dialog.
|
||||
* @param formConfig The form configuration.
|
||||
* */
|
||||
async openSendItemDialog(formConfig: SendFormConfig) {
|
||||
// Prevent multiple dialogs from being opened.
|
||||
if (this.sendItemDialogRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendItemDialogRef = SendAddEditComponent.open(this.dialogService, {
|
||||
formConfig,
|
||||
});
|
||||
|
||||
await lastValueFrom(dialog.closed);
|
||||
const result = await lastValueFrom(this.sendItemDialogRef.closed);
|
||||
this.sendItemDialogRef = undefined;
|
||||
|
||||
// If the dialog was closed by deleting the cipher, refresh the vault.
|
||||
if (result === SendItemDialogResult.Deleted || result === SendItemDialogResult.Saved) {
|
||||
await this.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +222,9 @@
|
||||
"notes": {
|
||||
"message": "Notes"
|
||||
},
|
||||
"privateNote": {
|
||||
"message": "Private note"
|
||||
},
|
||||
"note": {
|
||||
"message": "Note"
|
||||
},
|
||||
@@ -5105,12 +5108,40 @@
|
||||
"requireSsoExemption": {
|
||||
"message": "Organization owners and admins are exempt from this policy's enforcement."
|
||||
},
|
||||
"limitSendViews": {
|
||||
"message": "Limit views"
|
||||
},
|
||||
"limitSendViewsHint": {
|
||||
"message": "No one can view this Send after the limit is reached.",
|
||||
"description": "Displayed under the limit views field on Send"
|
||||
},
|
||||
"limitSendViewsCount": {
|
||||
"message": "$ACCESSCOUNT$ views left",
|
||||
"description": "Displayed under the limit views field on Send",
|
||||
"placeholders": {
|
||||
"accessCount": {
|
||||
"content": "$1",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sendDetails": {
|
||||
"message": "Send details",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendTypeTextToShare": {
|
||||
"message": "Text to share"
|
||||
},
|
||||
"sendTypeFile": {
|
||||
"message": "File"
|
||||
},
|
||||
"sendTypeText": {
|
||||
"message": "Text"
|
||||
},
|
||||
"sendPasswordDescV3": {
|
||||
"message": "Add an optional password for recipients to access this Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"createSend": {
|
||||
"message": "New Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
@@ -5135,19 +5166,15 @@
|
||||
"message": "Delete Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"deleteSendConfirmation": {
|
||||
"message": "Are you sure you want to delete this Send?",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"whatTypeOfSend": {
|
||||
"message": "What type of Send is this?",
|
||||
"deleteSendPermanentConfirmation": {
|
||||
"message": "Are you sure you want to permanently delete this Send?",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"deletionDate": {
|
||||
"message": "Deletion date"
|
||||
},
|
||||
"deletionDateDesc": {
|
||||
"message": "The Send will be permanently deleted on the specified date and time.",
|
||||
"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": {
|
||||
@@ -5160,21 +5187,6 @@
|
||||
"maxAccessCount": {
|
||||
"message": "Maximum access count"
|
||||
},
|
||||
"maxAccessCountDesc": {
|
||||
"message": "If set, users will no longer be able to access this Send once the maximum access count is reached.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"currentAccessCount": {
|
||||
"message": "Current access count"
|
||||
},
|
||||
"sendPasswordDesc": {
|
||||
"message": "Optionally require a password for users to access this Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendNotesDesc": {
|
||||
"message": "Private notes about this Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"disabled": {
|
||||
"message": "Disabled"
|
||||
},
|
||||
@@ -5201,13 +5213,6 @@
|
||||
"removePasswordConfirmation": {
|
||||
"message": "Are you sure you want to remove the password?"
|
||||
},
|
||||
"hideEmail": {
|
||||
"message": "Hide my email address from recipients."
|
||||
},
|
||||
"disableThisSend": {
|
||||
"message": "Deactivate this Send so that no one can access it.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"allSends": {
|
||||
"message": "All Sends"
|
||||
},
|
||||
@@ -5218,6 +5223,9 @@
|
||||
"pendingDeletion": {
|
||||
"message": "Pending deletion"
|
||||
},
|
||||
"hideTextByDefault": {
|
||||
"message": "Hide text by default"
|
||||
},
|
||||
"expired": {
|
||||
"message": "Expired"
|
||||
},
|
||||
@@ -5439,13 +5447,6 @@
|
||||
"message": "Always show member’s email address with recipients when creating or editing a Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendOptionsPolicyInEffect": {
|
||||
"message": "The following organization policies are currently in effect:"
|
||||
},
|
||||
"sendDisableHideEmailInEffect": {
|
||||
"message": "Users are not allowed to hide their email address from recipients when creating or editing a Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"modifiedPolicyId": {
|
||||
"message": "Modified policy $ID$.",
|
||||
"placeholders": {
|
||||
@@ -5545,27 +5546,6 @@
|
||||
"personalOwnershipCheckboxDesc": {
|
||||
"message": "Remove individual ownership for organization users"
|
||||
},
|
||||
"textHiddenByDefault": {
|
||||
"message": "When accessing the Send, hide the text by default",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendNameDesc": {
|
||||
"message": "A friendly name to describe this Send.",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"sendTextDesc": {
|
||||
"message": "The text you want to Send."
|
||||
},
|
||||
"sendFileDesc": {
|
||||
"message": "The file you want to Send."
|
||||
},
|
||||
"copySendLinkOnSave": {
|
||||
"message": "Copy the link to share this Send to my clipboard upon save."
|
||||
},
|
||||
"sendLinkLabel": {
|
||||
"message": "Send link",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
},
|
||||
"send": {
|
||||
"message": "Send",
|
||||
"description": "'Send' is a noun and the name of a feature called 'Bitwarden Send'. It should not be translated."
|
||||
@@ -5714,6 +5694,9 @@
|
||||
"dateParsingError": {
|
||||
"message": "There was an error saving your deletion and expiration dates."
|
||||
},
|
||||
"hideYourEmail": {
|
||||
"message": "Hide your email address from viewers."
|
||||
},
|
||||
"webAuthnFallbackMsg": {
|
||||
"message": "To verify your 2FA please click the button below."
|
||||
},
|
||||
@@ -9875,9 +9858,15 @@
|
||||
"learnMoreAboutApi": {
|
||||
"message": "Learn more about Bitwarden's API"
|
||||
},
|
||||
"fileSend": {
|
||||
"message": "File Send"
|
||||
},
|
||||
"fileSends": {
|
||||
"message": "File Sends"
|
||||
},
|
||||
"textSend": {
|
||||
"message": "Text Send"
|
||||
},
|
||||
"textSends": {
|
||||
"message": "Text Sends"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user