[PM-9926] Linked Fields order (#10174)
* refactor linkedFieldOption to take in an attributes object * add sort positions for existing linked fields * sort linked fields on new custom fields
This commit is contained in:
parent
88f585e09d
commit
decc7a3031
|
@ -1,11 +1,30 @@
|
||||||
import { LinkedIdType } from "./enums";
|
import { LinkedIdType } from "./enums";
|
||||||
import { ItemView } from "./models/view/item.view";
|
import { ItemView } from "./models/view/item.view";
|
||||||
|
|
||||||
|
type LinkedMetadataAttributes = {
|
||||||
|
/**
|
||||||
|
* The i18n key used to describe the decorated class property in the UI.
|
||||||
|
* If it is null, then the name of the class property will be used as the i18n key.
|
||||||
|
*/
|
||||||
|
i18nKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position of the individual field to be applied when sorted.
|
||||||
|
*/
|
||||||
|
sortPosition: number;
|
||||||
|
};
|
||||||
|
|
||||||
export class LinkedMetadata {
|
export class LinkedMetadata {
|
||||||
|
private readonly _i18nKey: string;
|
||||||
|
readonly sortPosition: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly propertyKey: string,
|
readonly propertyKey: string,
|
||||||
private readonly _i18nKey?: string,
|
attributes: LinkedMetadataAttributes,
|
||||||
) {}
|
) {
|
||||||
|
this._i18nKey = attributes?.i18nKey;
|
||||||
|
this.sortPosition = attributes.sortPosition;
|
||||||
|
}
|
||||||
|
|
||||||
get i18nKey() {
|
get i18nKey() {
|
||||||
return this._i18nKey ?? this.propertyKey;
|
return this._i18nKey ?? this.propertyKey;
|
||||||
|
@ -16,15 +35,14 @@ export class LinkedMetadata {
|
||||||
* A decorator used to set metadata used by Linked custom fields. Apply it to a class property or getter to make it
|
* A decorator used to set metadata used by Linked custom fields. Apply it to a class property or getter to make it
|
||||||
* available as a Linked custom field option.
|
* available as a Linked custom field option.
|
||||||
* @param id - A unique value that is saved in the Field model. It is used to look up the decorated class property.
|
* @param id - A unique value that is saved in the Field model. It is used to look up the decorated class property.
|
||||||
* @param i18nKey - The i18n key used to describe the decorated class property in the UI. If it is null, then the name
|
* @param options - {@link LinkedMetadataAttributes}
|
||||||
* of the class property will be used as the i18n key.
|
|
||||||
*/
|
*/
|
||||||
export function linkedFieldOption(id: LinkedIdType, i18nKey?: string) {
|
export function linkedFieldOption(id: LinkedIdType, attributes: LinkedMetadataAttributes) {
|
||||||
return (prototype: ItemView, propertyKey: string) => {
|
return (prototype: ItemView, propertyKey: string) => {
|
||||||
if (prototype.linkedFieldOptions == null) {
|
if (prototype.linkedFieldOptions == null) {
|
||||||
prototype.linkedFieldOptions = new Map<LinkedIdType, LinkedMetadata>();
|
prototype.linkedFieldOptions = new Map<LinkedIdType, LinkedMetadata>();
|
||||||
}
|
}
|
||||||
|
|
||||||
prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey));
|
prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, attributes));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
|
||||||
import { ItemView } from "./item.view";
|
import { ItemView } from "./item.view";
|
||||||
|
|
||||||
export class CardView extends ItemView {
|
export class CardView extends ItemView {
|
||||||
@linkedFieldOption(LinkedId.CardholderName)
|
@linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 })
|
||||||
cardholderName: string = null;
|
cardholderName: string = null;
|
||||||
@linkedFieldOption(LinkedId.ExpMonth, "expirationMonth")
|
@linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" })
|
||||||
expMonth: string = null;
|
expMonth: string = null;
|
||||||
@linkedFieldOption(LinkedId.ExpYear, "expirationYear")
|
@linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" })
|
||||||
expYear: string = null;
|
expYear: string = null;
|
||||||
@linkedFieldOption(LinkedId.Code, "securityCode")
|
@linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" })
|
||||||
code: string = null;
|
code: string = null;
|
||||||
|
|
||||||
private _brand: string = null;
|
private _brand: string = null;
|
||||||
|
@ -27,7 +27,7 @@ export class CardView extends ItemView {
|
||||||
return this.number != null ? "•".repeat(this.number.length) : null;
|
return this.number != null ? "•".repeat(this.number.length) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@linkedFieldOption(LinkedId.Brand)
|
@linkedFieldOption(LinkedId.Brand, { sortPosition: 2 })
|
||||||
get brand(): string {
|
get brand(): string {
|
||||||
return this._brand;
|
return this._brand;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ export class CardView extends ItemView {
|
||||||
this._subTitle = null;
|
this._subTitle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@linkedFieldOption(LinkedId.Number)
|
@linkedFieldOption(LinkedId.Number, { sortPosition: 1 })
|
||||||
get number(): string {
|
get number(): string {
|
||||||
return this._number;
|
return this._number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,37 +7,37 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
|
||||||
import { ItemView } from "./item.view";
|
import { ItemView } from "./item.view";
|
||||||
|
|
||||||
export class IdentityView extends ItemView {
|
export class IdentityView extends ItemView {
|
||||||
@linkedFieldOption(LinkedId.Title)
|
@linkedFieldOption(LinkedId.Title, { sortPosition: 0 })
|
||||||
title: string = null;
|
title: string = null;
|
||||||
@linkedFieldOption(LinkedId.MiddleName)
|
@linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 })
|
||||||
middleName: string = null;
|
middleName: string = null;
|
||||||
@linkedFieldOption(LinkedId.Address1)
|
@linkedFieldOption(LinkedId.Address1, { sortPosition: 12 })
|
||||||
address1: string = null;
|
address1: string = null;
|
||||||
@linkedFieldOption(LinkedId.Address2)
|
@linkedFieldOption(LinkedId.Address2, { sortPosition: 13 })
|
||||||
address2: string = null;
|
address2: string = null;
|
||||||
@linkedFieldOption(LinkedId.Address3)
|
@linkedFieldOption(LinkedId.Address3, { sortPosition: 14 })
|
||||||
address3: string = null;
|
address3: string = null;
|
||||||
@linkedFieldOption(LinkedId.City, "cityTown")
|
@linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" })
|
||||||
city: string = null;
|
city: string = null;
|
||||||
@linkedFieldOption(LinkedId.State, "stateProvince")
|
@linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" })
|
||||||
state: string = null;
|
state: string = null;
|
||||||
@linkedFieldOption(LinkedId.PostalCode, "zipPostalCode")
|
@linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" })
|
||||||
postalCode: string = null;
|
postalCode: string = null;
|
||||||
@linkedFieldOption(LinkedId.Country)
|
@linkedFieldOption(LinkedId.Country, { sortPosition: 18 })
|
||||||
country: string = null;
|
country: string = null;
|
||||||
@linkedFieldOption(LinkedId.Company)
|
@linkedFieldOption(LinkedId.Company, { sortPosition: 6 })
|
||||||
company: string = null;
|
company: string = null;
|
||||||
@linkedFieldOption(LinkedId.Email)
|
@linkedFieldOption(LinkedId.Email, { sortPosition: 10 })
|
||||||
email: string = null;
|
email: string = null;
|
||||||
@linkedFieldOption(LinkedId.Phone)
|
@linkedFieldOption(LinkedId.Phone, { sortPosition: 11 })
|
||||||
phone: string = null;
|
phone: string = null;
|
||||||
@linkedFieldOption(LinkedId.Ssn)
|
@linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 })
|
||||||
ssn: string = null;
|
ssn: string = null;
|
||||||
@linkedFieldOption(LinkedId.Username)
|
@linkedFieldOption(LinkedId.Username, { sortPosition: 5 })
|
||||||
username: string = null;
|
username: string = null;
|
||||||
@linkedFieldOption(LinkedId.PassportNumber)
|
@linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 })
|
||||||
passportNumber: string = null;
|
passportNumber: string = null;
|
||||||
@linkedFieldOption(LinkedId.LicenseNumber)
|
@linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 })
|
||||||
licenseNumber: string = null;
|
licenseNumber: string = null;
|
||||||
|
|
||||||
private _firstName: string = null;
|
private _firstName: string = null;
|
||||||
|
@ -48,7 +48,7 @@ export class IdentityView extends ItemView {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@linkedFieldOption(LinkedId.FirstName)
|
@linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 })
|
||||||
get firstName(): string {
|
get firstName(): string {
|
||||||
return this._firstName;
|
return this._firstName;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export class IdentityView extends ItemView {
|
||||||
this._subTitle = null;
|
this._subTitle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@linkedFieldOption(LinkedId.LastName)
|
@linkedFieldOption(LinkedId.LastName, { sortPosition: 4 })
|
||||||
get lastName(): string {
|
get lastName(): string {
|
||||||
return this._lastName;
|
return this._lastName;
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ export class IdentityView extends ItemView {
|
||||||
return this._subTitle;
|
return this._subTitle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@linkedFieldOption(LinkedId.FullName)
|
@linkedFieldOption(LinkedId.FullName, { sortPosition: 3 })
|
||||||
get fullName(): string {
|
get fullName(): string {
|
||||||
if (
|
if (
|
||||||
this.title != null ||
|
this.title != null ||
|
||||||
|
|
|
@ -10,9 +10,9 @@ import { ItemView } from "./item.view";
|
||||||
import { LoginUriView } from "./login-uri.view";
|
import { LoginUriView } from "./login-uri.view";
|
||||||
|
|
||||||
export class LoginView extends ItemView {
|
export class LoginView extends ItemView {
|
||||||
@linkedFieldOption(LinkedId.Username)
|
@linkedFieldOption(LinkedId.Username, { sortPosition: 0 })
|
||||||
username: string = null;
|
username: string = null;
|
||||||
@linkedFieldOption(LinkedId.Password)
|
@linkedFieldOption(LinkedId.Password, { sortPosition: 1 })
|
||||||
password: string = null;
|
password: string = null;
|
||||||
|
|
||||||
passwordRevisionDate?: Date = null;
|
passwordRevisionDate?: Date = null;
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { Subject, zip } from "rxjs";
|
||||||
|
|
||||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
||||||
import { CipherType, FieldType, LinkedIdType } from "@bitwarden/common/vault/enums";
|
import { CipherType, FieldType, LinkedIdType } from "@bitwarden/common/vault/enums";
|
||||||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||||||
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
|
||||||
|
@ -135,13 +134,14 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const linkedFieldsOptionsForCipher = this.getLinkedFieldsOptionsForCipher();
|
const linkedFieldsOptionsForCipher = this.getLinkedFieldsOptionsForCipher();
|
||||||
|
const optionsArray = Array.from(linkedFieldsOptionsForCipher?.entries() ?? []);
|
||||||
|
optionsArray.sort((a, b) => a[1].sortPosition - b[1].sortPosition);
|
||||||
|
|
||||||
// Populate options for linked custom fields
|
// Populate options for linked custom fields
|
||||||
this.linkedFieldOptions = Array.from(linkedFieldsOptionsForCipher?.entries() ?? [])
|
this.linkedFieldOptions = optionsArray.map(([id, linkedFieldOption]) => ({
|
||||||
.map(([id, linkedFieldOption]) => ({
|
name: this.i18nService.t(linkedFieldOption.i18nKey),
|
||||||
name: this.i18nService.t(linkedFieldOption.i18nKey),
|
value: id,
|
||||||
value: id,
|
}));
|
||||||
}))
|
|
||||||
.sort(Utils.getSortFunction(this.i18nService, "name"));
|
|
||||||
|
|
||||||
// Populate the form with the existing fields
|
// Populate the form with the existing fields
|
||||||
this.cipherFormContainer.originalCipherView?.fields?.forEach((field) => {
|
this.cipherFormContainer.originalCipherView?.fields?.forEach((field) => {
|
||||||
|
|
Loading…
Reference in New Issue