mirror of
https://github.com/bitwarden/browser
synced 2026-02-19 10:54:00 +00:00
Existing custom fields
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user