From 883fd75217314f3db98b5ef47ceae3310c4c27de Mon Sep 17 00:00:00 2001 From: David Murdoch <187813+davidmurdoch@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:39:40 -0400 Subject: [PATCH] refactor: convert `icon-factory.js` to typescript (#23823) --- ui/helpers/utils/icon-factory.js | 69 --------------- ui/helpers/utils/icon-factory.ts | 141 +++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 69 deletions(-) delete mode 100644 ui/helpers/utils/icon-factory.js create mode 100644 ui/helpers/utils/icon-factory.ts diff --git a/ui/helpers/utils/icon-factory.js b/ui/helpers/utils/icon-factory.js deleted file mode 100644 index db5140756d04..000000000000 --- a/ui/helpers/utils/icon-factory.js +++ /dev/null @@ -1,69 +0,0 @@ -import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; - -let iconFactory; - -export default function iconFactoryGenerator(jazzicon) { - if (!iconFactory) { - iconFactory = new IconFactory(jazzicon); - } - return iconFactory; -} - -function IconFactory(jazzicon) { - this.jazzicon = jazzicon; - this.cache = {}; -} - -IconFactory.prototype.iconForAddress = function ( - address, - diameter, - tokenMetadata, -) { - if (iconExistsFor(address, tokenMetadata)) { - return imageElFor(tokenMetadata); - } - - return this.generateIdenticonSvg(address, diameter); -}; - -// returns svg dom element -IconFactory.prototype.generateIdenticonSvg = function (address, diameter) { - const cacheId = `${address}:${diameter}`; - // check cache, lazily generate and populate cache - const identicon = - this.cache[cacheId] || - (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)); - // create a clean copy so you can modify it - const cleanCopy = identicon.cloneNode(true); - return cleanCopy; -}; - -// creates a new identicon -IconFactory.prototype.generateNewIdenticon = function (address, diameter) { - const numericRepresentation = jsNumberForAddress(address); - const identicon = this.jazzicon(diameter, numericRepresentation); - return identicon; -}; - -// util - -function iconExistsFor(address, tokenMetadata) { - return ( - isValidHexAddress(address, { allowNonPrefixed: false }) && - tokenMetadata && - tokenMetadata.iconUrl - ); -} - -function imageElFor(tokenMetadata = {}) { - const img = document.createElement('img'); - img.src = tokenMetadata?.iconUrl; - img.style.width = '100%'; - return img; -} - -function jsNumberForAddress(address) { - const addr = address.slice(2, 10); - const seed = parseInt(addr, 16); - return seed; -} diff --git a/ui/helpers/utils/icon-factory.ts b/ui/helpers/utils/icon-factory.ts new file mode 100644 index 000000000000..bd7a519ef389 --- /dev/null +++ b/ui/helpers/utils/icon-factory.ts @@ -0,0 +1,141 @@ +import { isValidHexAddress } from '../../../shared/modules/hexstring-utils'; + +/** + * Defines the metadata for a token including optional icon URL. + */ +type TokenMetadata = { + iconUrl: string; +}; + +/** + * A factory for generating icons for cryptocurrency addresses using Jazzicon or predefined token metadata. + */ +class IconFactory { + /** + * Function to generate a Jazzicon SVG element. + */ + jazzicon: (diameter: number, seed: number) => SVGSVGElement; + + /** + * Cache for storing generated SVG elements to avoid re-rendering. + */ + cache: { [key: string]: SVGSVGElement }; + + /** + * Constructs an IconFactory instance with a given Jazzicon function. + * + * @param jazzicon - A function that returns a Jazzicon SVG given a diameter and seed. + */ + constructor(jazzicon: (diameter: number, seed: number) => SVGSVGElement) { + this.jazzicon = jazzicon; + this.cache = {}; + } + + /** + * Generates an icon for a given address. Returns a predefined image or generates a new Jazzicon. + * + * @param address - The cryptocurrency address to generate the icon for. + * @param diameter - The diameter of the icon to be generated. + * @param tokenMetadata - Metadata containing optional icon URL for predefined icons. + * @returns An HTML element representing the icon. + */ + iconForAddress( + address: string, + diameter: number, + tokenMetadata?: Partial, + ): HTMLElement | SVGSVGElement { + if (iconExistsFor(address, tokenMetadata)) { + return imageElFor(tokenMetadata); + } + + return this.generateIdenticonSvg(address, diameter); + } + + /** + * Generates or retrieves from cache a Jazzicon SVG for a given address and diameter. + * + * @param address - The cryptocurrency address for the identicon. + * @param diameter - The diameter of the identicon. + * @returns A Jazzicon SVG element. + */ + generateIdenticonSvg(address: string, diameter: number): SVGSVGElement { + const cacheId = `${address}:${diameter}`; + const identicon: SVGSVGElement = + this.cache[cacheId] || + (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)); + const cleanCopy: SVGSVGElement = identicon.cloneNode(true) as SVGSVGElement; + return cleanCopy; + } + + /** + * Generates a new Jazzicon SVG for a given address and diameter. + * + * @param address - The cryptocurrency address for the identicon. + * @param diameter - The diameter of the identicon. + * @returns A new Jazzicon SVG element. + */ + generateNewIdenticon(address: string, diameter: number): SVGSVGElement { + const numericRepresentation = jsNumberForAddress(address); + const identicon = this.jazzicon(diameter, numericRepresentation); + return identicon; + } +} + +let iconFactory: IconFactory | undefined; + +/** + * Generates or retrieves an existing IconFactory instance. + * + * @param jazzicon - A function that returns a Jazzicon SVG given a diameter and seed. + * @returns An IconFactory instance. + */ +export default function iconFactoryGenerator( + jazzicon: (diameter: number, seed: number) => SVGSVGElement, +): IconFactory { + if (!iconFactory) { + iconFactory = new IconFactory(jazzicon); + } + return iconFactory; +} + +/** + * Determines if an icon already exists for a given address based on token metadata. + * + * @param address - The cryptocurrency address. + * @param tokenMetadata - Metadata containing optional icon URL. + * @returns True if an icon exists, otherwise false. + */ +function iconExistsFor( + address: string, + tokenMetadata?: Partial, +): tokenMetadata is TokenMetadata { + if (!tokenMetadata?.iconUrl) { + return false; + } + return isValidHexAddress(address, { allowNonPrefixed: false }); +} + +/** + * Creates an HTMLImageElement for a given token metadata. + * + * @param tokenMetadata - Metadata containing the icon URL. Defaults to an empty object. + * @returns An HTMLImageElement with the source set to the icon URL. + */ +function imageElFor(tokenMetadata: TokenMetadata): HTMLImageElement { + const img = document.createElement('img'); + img.src = tokenMetadata.iconUrl; + img.style.width = '100%'; + return img; +} + +/** + * Converts a hexadecimal address into a numerical seed. + * + * @param address - The cryptocurrency address. + * @returns A numerical seed derived from the address. + */ +function jsNumberForAddress(address: string): number { + const addr = address.slice(2, 10); + const seed = parseInt(addr, 16); + return seed; +}