diff --git a/src/components/addresses/picker/AddressPicker.less b/src/components/addresses/picker/AddressPicker.less index 82352d0..b25e5d9 100644 --- a/src/components/addresses/picker/AddressPicker.less +++ b/src/components/addresses/picker/AddressPicker.less @@ -29,12 +29,13 @@ flex: 1; } - .address-picker-wallet-label { + .address-picker-wallet-label, .address-picker-contact-label { white-space: normal; word-break: break-word; } - .address-picker-wallet-label + .address-picker-wallet-address { + .address-picker-wallet-label + .address-picker-wallet-address, + .address-picker-contact-label + .address-picker-contact-address { color: @text-color-secondary; } } diff --git a/src/components/addresses/picker/AddressPicker.tsx b/src/components/addresses/picker/AddressPicker.tsx index bd66f64..4b08d4f 100644 --- a/src/components/addresses/picker/AddressPicker.tsx +++ b/src/components/addresses/picker/AddressPicker.tsx @@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next"; import { useWallets } from "@wallets"; +import { useContacts } from "@contacts"; import { useAddressPrefix, useNameSuffix, isValidAddress, getNameParts, @@ -78,7 +79,9 @@ // (and soon the address book too), but to save on time and expense, the // 'exact address' match is prepended to these options dynamically. const { wallets, addressList } = useWallets(); - const options = useMemo(() => getOptions(t, wallets), [t, wallets]); + const { contacts, contactAddressList } = useContacts(); + const options = useMemo(() => getOptions(t, wallets, contacts), + [t, wallets, contacts]); // Check if the input text is an exact address. If it is, create an extra item // to prepend to the list. Note that the 'exact address' item is NOT shown if @@ -241,6 +244,7 @@ const address = option!.value?.toUpperCase(); const walletLabel = option!["data-wallet-label"]?.toUpperCase(); + const contactLabel = option!["data-contact-label"]?.toUpperCase(); // If we have another address picker's value, hide that option from // the list (it will always be a wallet) @@ -258,8 +262,9 @@ const matchedAddress = address.indexOf(inp) !== -1; const matchedLabel = walletLabel?.indexOf(inp) !== -1; + const matchedContactLabel = contactLabel?.indexOf(inp) !== -1; - return matchedAddress || matchedLabel; + return matchedAddress || matchedLabel || matchedContactLabel; }} options={fullOptions} diff --git a/src/components/addresses/picker/Item.tsx b/src/components/addresses/picker/Item.tsx index f4b2e21..793deea 100644 --- a/src/components/addresses/picker/Item.tsx +++ b/src/components/addresses/picker/Item.tsx @@ -3,6 +3,7 @@ // Full details: https://github.com/tmpim/KristWeb2/blob/master/LICENSE.txt import { Wallet } from "@wallets"; +import { Contact } from "@contacts"; import { NameParts } from "@utils/currency"; import { KristValue } from "@comp/krist/KristValue"; @@ -13,10 +14,17 @@ address?: string; name?: NameParts; wallet?: Wallet; + contact?: Contact; } -function getPlainAddress({ address, name, wallet }: AddressItemProps): string { +function getPlainAddress({ + address, + name, + wallet, + contact +}: AddressItemProps): string { if (wallet) return wallet.address; + if (contact) return contact.address; if (name?.recipient) return name.recipient; else return address || ""; } @@ -24,14 +32,21 @@ function PickerContent({ name, wallet, + contact, plainAddress }: AddressItemProps & { plainAddress: string }): JSX.Element { - if (wallet && wallet.label) { + if (wallet?.label) { // Show the wallet label if possible return <> {wallet.label}  ({wallet.address}) ; + } else if (contact?.label) { + // Show the contact label if possible + return <> + {contact.label}  + ({contact.address}) + ; } else if (name?.recipient) { // Show a formatted name if possible const { metaname, nameWithSuffix } = name; @@ -49,12 +64,12 @@ export function getAddressItem(props: AddressItemProps): OptionValue { // The address to use as a value const plainAddress = getPlainAddress(props); - const { wallet } = props; + const { wallet, contact } = props; return { label: (
- {/* Address, wallet label, or name */} + {/* Address, wallet label, contact label, or name */}
@@ -66,8 +81,10 @@ // The wallet label is used for filtering the options "data-wallet-label": wallet?.label, + "data-contact-label": contact?.label, // The wallet itself is used for sorting the options "data-wallet": wallet, + "data-contact": contact, value: plainAddress }; diff --git a/src/components/addresses/picker/options.ts b/src/components/addresses/picker/options.ts index 0b9aff5..9c9c231 100644 --- a/src/components/addresses/picker/options.ts +++ b/src/components/addresses/picker/options.ts @@ -4,6 +4,7 @@ import { TFunction } from "react-i18next"; import { WalletMap, Wallet } from "@wallets"; +import { ContactMap, Contact } from "@contacts"; import { getCategoryHeader } from "./Header"; import { getAddressItem } from "./Item"; @@ -21,7 +22,9 @@ // For some reason, all these props get passed all the way to the DOM element! // Make this a 'valid' DOM prop "data-wallet-label"?: string; + "data-contact-label"?: string; "data-wallet"?: Wallet; + "data-contact"?: Contact; value: string; } @@ -39,6 +42,7 @@ interface WalletOptions { categorised: Record; uncategorised: OptionValue[]; + contacts: OptionValue[]; categoryCount: number; } @@ -46,18 +50,30 @@ // the bottom by using keyedNullSort. Addresses are sorted ascending, though // because of the implicit reversing behaviour of keyedNullSort, they need to // be swapped here (i.e. sort with `b`, `a`). -const sortBalance = keyedNullSort("balance"); -const sortAddress = keyedNullSort("address", true); +const sortBalance = keyedNullSort<{ balance?: number }>("balance"); +const sortLabel = keyedNullSort<{ label?: string }>("label", true); +const sortAddress = keyedNullSort<{ address: string }>("address", true); + const sortFn = (a: Wallet, b: Wallet): number => sortBalance(a, b, "descend") || sortAddress(b, a); const optionSortFn = (a: OptionValue, b: OptionValue): number => sortFn(a["data-wallet"]!, b["data-wallet"]!); +const contactSortFn = (a: Contact, b: Contact): number => + sortLabel(b, a) || sortAddress(b, a); +const contactOptionSortFn = (a: OptionValue, b: OptionValue): number => + contactSortFn(a["data-contact"]!, b["data-contact"]!); + /** Groups the wallets by category for autocompletion and generates their select * options. */ -function getWalletOptions(wallets: WalletMap): WalletOptions { +function getWalletOptions( + wallets: WalletMap, + contacts: ContactMap +): WalletOptions { const categorised: Record = {}; const uncategorised: OptionValue[] = []; + const contactValues: OptionValue[] = Object.values(contacts) + .map(contact => getAddressItem({ contact })); // Go through all wallets and group them for (const id in wallets) { @@ -84,12 +100,17 @@ categorised[category].sort(optionSortFn); categorised[category].reverse(); } + uncategorised.sort(optionSortFn); uncategorised.reverse(); + contactValues.sort(contactOptionSortFn); + contactValues.reverse(); + return { categorised, uncategorised, + contacts: contactValues, categoryCount: Object.keys(categorised).length }; } @@ -99,10 +120,14 @@ // ----------------------------------------------------------------------------- /** Gets the base options to show for autocompletion, including the wallets, * grouped by category if possible. Will include the address book soon too. */ -export function getOptions(t: TFunction, wallets: WalletMap): Option[] { +export function getOptions( + t: TFunction, + wallets: WalletMap, + contactMap: ContactMap, +): Option[] { // Wallet options - const { categorised, uncategorised, categoryCount } - = getWalletOptions(wallets); + const { categorised, uncategorised, categoryCount, contacts } + = getWalletOptions(wallets, contactMap); // Sort the wallet categories in a human-friendly manner const sortedCategories = Object.keys(categorised); @@ -130,6 +155,10 @@ options: uncategorised }, - // TODO: Address book + // Address book + { + ...getCategoryHeader(t("addressPicker.categoryAddressBook")), + options: contacts + }, ]; } diff --git a/src/pages/addresses/AddressButtonRow.tsx b/src/pages/addresses/AddressButtonRow.tsx index 1fa174c..638914e 100644 --- a/src/pages/addresses/AddressButtonRow.tsx +++ b/src/pages/addresses/AddressButtonRow.tsx @@ -54,7 +54,6 @@ )} {/* Add contact/edit wallet button */} - {/* TODO: Change this to edit if they're already a contact */} {myWallet ? (