1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-19 10:54:00 +00:00

Existing custom fields

This commit is contained in:
Matt Gibson
2025-03-20 16:17:52 -07:00
parent 6311e8b566
commit c5b6dd1bcd
4 changed files with 143 additions and 10 deletions

View File

@@ -269,6 +269,10 @@ import {
} from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/vault/abstractions/totp.service";
import { VaultSettingsService as VaultSettingsServiceAbstraction } from "@bitwarden/common/vault/abstractions/vault-settings/vault-settings.service";
import {
DefaultVaultFilterMetadataService,
VaultFilterMetadataService,
} from "@bitwarden/common/vault/filtering/vault-filter-metadata.service";
import { DefaultFilterService, FilterService } from "@bitwarden/common/vault/search/filter.service";
import {
SavedFiltersService,
@@ -1494,6 +1498,11 @@ const safeProviders: SafeProvider[] = [
useClass: DefaultSavedFiltersService,
deps: [SingleUserStateProvider, EncryptService, KeyService],
}),
safeProvider({
provide: VaultFilterMetadataService,
useClass: DefaultVaultFilterMetadataService,
deps: [],
}),
];
@NgModule({

View File

@@ -1,4 +1,4 @@
import { map } from "rxjs";
import { OperatorFunction, map } from "rxjs";
import { CipherType, FieldType, LinkedIdType } from "../enums";
import { CipherView } from "../models/view/cipher.view";
@@ -30,7 +30,11 @@ function metaDataKeyEqual<T extends MetadataType>(a: T, b: T) {
}
}
export class VaultFilterMetadataService {
export abstract class VaultFilterMetadataService {
abstract collectMetadata(): OperatorFunction<CipherView[], VaultFilterMetadata>;
}
export class DefaultVaultFilterMetadataService implements VaultFilterMetadataService {
collectMetadata() {
const setOrIncrement = <T extends MetadataType>(map: Map<T, number>, key: T) => {
const entry = Array.from(map.entries()).find(([k]) => metaDataKeyEqual(key, k));

View File

@@ -1,10 +1,39 @@
<form [formGroup]="customFieldForm" [bitSubmit]="submit">
<bit-dialog>
<bit-dialog *ngIf="variant === 'add'">
<span bitDialogTitle>
{{ (variant === "add" ? "addField" : "editField") | i18n }}
{{ "addField" | i18n }}
</span>
<div bitDialogContent>
<bit-form-field *ngIf="variant === 'add'">
<div class="tw-flex tw-mb-2 tw-justify-center">
<bit-toggle-group class="tw-flex-grow tw-flex" selected="basic">
<bit-toggle
class="tw-flex-grow tw-max-w-lg"
value="basic"
(click)="setSelectExistingField(false)"
>New</bit-toggle
>
<bit-toggle
class="tw-flex-grow tw-max-w-lg"
value="advanced"
(click)="setSelectExistingField(true)"
>Existing</bit-toggle
>
</bit-toggle-group>
</div>
<bit-form-field *ngIf="selectExistingField">
<bit-select id="fieldName" formControlName="selectedExistingField">
<bit-option
*ngFor="let field of existingFields$ | async"
[value]="field"
[label]="field.name"
>
{{ field.name }}:{{ field.type }}({{ field.count }})
</bit-option>
</bit-select>
</bit-form-field>
<bit-form-field [hidden]="selectExistingField">
<bit-label>{{ "fieldType" | i18n }}</bit-label>
<bit-select id="fieldType" formControlName="type">
<bit-option
@@ -18,6 +47,28 @@
</bit-hint>
</bit-form-field>
<bit-form-field [hidden]="selectExistingField" disableMargin>
<bit-label>{{ "fieldLabel" | i18n }}</bit-label>
<input bitInput id="fieldLabel" formControlName="label" type="text" />
<bit-hint *ngIf="customFieldForm.value.type === FieldType.Linked">
{{ "linkedLabelHelpText" | i18n }}
</bit-hint>
</bit-form-field>
</div>
<div bitDialogFooter class="tw-flex tw-gap-2 tw-w-full">
<button bitButton buttonType="primary" type="submit" [disabled]="customFieldForm.invalid">
{{ "add" | i18n }}
</button>
<button bitButton bitDialogClose buttonType="secondary" type="button">
{{ "cancel" | i18n }}
</button>
</div>
</bit-dialog>
<bit-dialog *ngIf="variant === 'edit'">
<span bitDialogTitle>
{{ "editField" | i18n }}
</span>
<div bitDialogContent>
<bit-form-field disableMargin>
<bit-label>{{ "fieldLabel" | i18n }}</bit-label>
<input bitInput id="fieldLabel" formControlName="label" type="text" />
@@ -28,14 +79,13 @@
</div>
<div bitDialogFooter class="tw-flex tw-gap-2 tw-w-full">
<button bitButton buttonType="primary" type="submit" [disabled]="customFieldForm.invalid">
{{ (variant === "add" ? "add" : "save") | i18n }}
{{ "save" | i18n }}
</button>
<button bitButton bitDialogClose buttonType="secondary" type="button">
{{ "cancel" | i18n }}
</button>
<button
*ngIf="variant === 'edit'"
type="button"
buttonType="danger"
class="tw-ml-auto"

View File

@@ -2,12 +2,19 @@
// @ts-strict-ignore
import { DIALOG_DATA } from "@angular/cdk/dialog";
import { CommonModule } from "@angular/common";
import { Component, Inject } from "@angular/core";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { Observable, Subject, map, of, switchMap, takeUntil } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherType, FieldType } from "@bitwarden/common/vault/enums";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType, FieldType, LinkedIdType } from "@bitwarden/common/vault/enums";
import {
CustomFieldMetadata,
VaultFilterMetadataService,
} from "@bitwarden/common/vault/filtering/vault-filter-metadata.service";
import {
AsyncActionsModule,
ButtonModule,
@@ -15,6 +22,7 @@ import {
FormFieldModule,
IconButtonModule,
SelectModule,
ToggleGroupModule,
} from "@bitwarden/components";
export type AddEditCustomFieldDialogData = {
@@ -41,12 +49,14 @@ export type AddEditCustomFieldDialogData = {
ReactiveFormsModule,
IconButtonModule,
AsyncActionsModule,
ToggleGroupModule,
],
})
export class AddEditCustomFieldDialogComponent {
export class AddEditCustomFieldDialogComponent implements OnInit, OnDestroy {
variant: "add" | "edit";
customFieldForm = this.formBuilder.group({
selectedExistingField: null as CustomFieldMetadata,
type: FieldType.Text,
label: ["", Validators.required],
});
@@ -60,10 +70,19 @@ export class AddEditCustomFieldDialogComponent {
FieldType = FieldType;
protected selectExistingField = false;
protected existingFields$: Observable<
{ name: string; type: FieldType; linkedType: LinkedIdType; count: number }[]
>;
private destroy$ = new Subject<void>();
constructor(
@Inject(DIALOG_DATA) private data: AddEditCustomFieldDialogData,
private formBuilder: FormBuilder,
private i18nService: I18nService,
private AccountService: AccountService,
private cipherService: CipherService,
private vaultFilterMetadataService: VaultFilterMetadataService,
) {
this.variant = data.editLabelConfig ? "edit" : "add";
@@ -80,6 +99,44 @@ export class AddEditCustomFieldDialogComponent {
this.customFieldForm.controls.label.setValue(data.editLabelConfig.label);
this.customFieldForm.controls.type.disable();
}
this.existingFields$ = this.AccountService.activeAccount$.pipe(
switchMap((account) => {
if (!account) {
return of([]);
}
return this.cipherService.cipherViews$(account.id);
}),
this.vaultFilterMetadataService.collectMetadata(),
map((metadata) => metadata.customFields),
map((customFields) => {
return Array.from(customFields.entries()).map(([key, count]) => {
const { name, type, linkedType } = key;
return {
name,
type,
linkedType,
count,
};
});
}),
);
}
ngOnInit(): void {
this.customFieldForm.controls.selectedExistingField.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe((selectedExistingField) => {
if (selectedExistingField) {
this.customFieldForm.controls.label.setValue(selectedExistingField.name);
this.customFieldForm.controls.type.setValue(selectedExistingField.type);
}
});
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
getTypeHint(): string {
@@ -130,4 +187,17 @@ export class AddEditCustomFieldDialogComponent {
removeField() {
this.data.removeField(this.data.editLabelConfig.index);
}
setSelectExistingField(existingField: boolean) {
this.selectExistingField = existingField;
}
setFormValuesFromExistingField() {
this.customFieldForm.controls.type.setValue(
this.customFieldForm.controls.selectedExistingField.value.type,
);
this.customFieldForm.controls.label.setValue(
this.customFieldForm.controls.selectedExistingField.value.name,
);
}
}