@metamask/design-system-react-native
Version:
203 lines • 7.13 kB
JavaScript
import { extractAccountAddress, generateIconSeed } from "@metamask/design-system-shared";
// /////////////////////////////////////////////////////
// Shared utilities have been moved to @metamask/design-system-shared
// /////////////////////////////////////////////////////
// /////////////////////////////////////////////////////
// Maskicon SVG Creation
// /////////////////////////////////////////////////////
// Color Palettes
const neutralPairs = [
['#FF5C16', '#FCFCFC'],
['#FF5C16', '#131416'],
['#D075FF', '#FCFCFC'],
['#D075FF', '#131416'],
['#BAF24A', '#FCFCFC'],
['#BAF24A', '#131416'],
['#89B0FF', '#FCFCFC'],
['#89B0FF', '#131416'],
['#FCFCFC', '#FF5C16'],
['#131416', '#FF5C16'],
['#FCFCFC', '#D075FF'],
['#131416', '#D075FF'],
['#FCFCFC', '#BAF24A'],
['#131416', '#BAF24A'],
['#FCFCFC', '#89B0FF'],
['#131416', '#89B0FF'],
];
const tonalPairs = [
['#FFA680', '#FF5C16'],
['#661800', '#FF5C16'],
['#EAC2FF', '#D075FF'],
['#3D065F', '#D075FF'],
['#E5FFC3', '#BAF24A'],
['#013330', '#BAF24A'],
['#CCE7FF', '#89B0FF'],
['#190066', '#89B0FF'],
['#FF5C16', '#FFA680'],
['#FF5C16', '#661800'],
['#D075FF', '#EAC2FF'],
['#D075FF', '#3D065F'],
['#BAF24A', '#E5FFC3'],
['#BAF24A', '#013330'],
['#89B0FF', '#CCE7FF'],
['#89B0FF', '#190066'],
['#661800', '#FFA680'],
['#FFA680', '#661800'],
['#3D065F', '#EAC2FF'],
['#EAC2FF', '#3D065F'],
['#013330', '#E5FFC3'],
['#E5FFC3', '#013330'],
['#190066', '#CCE7FF'],
['#CCE7FF', '#190066'],
];
const complementaryPairs = [
['#EAC2FF', '#013330'],
['#013330', '#EAC2FF'],
['#CCE7FF', '#661800'],
['#661800', '#CCE7FF'],
['#E5FFC3', '#3D065F'],
['#3D065F', '#E5FFC3'],
['#FFA680', '#190066'],
['#190066', '#FFA680'],
['#CCE7FF', '#013330'],
['#013330', '#CCE7FF'],
];
const colorPairs = neutralPairs.concat(tonalPairs).concat(complementaryPairs);
/**
* SDBM hash function
*
* @param str The string to hash
* @returns A numeric hash value
*/
export function sdbmHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
// eslint-disable-next-line no-bitwise
hash = str.charCodeAt(i) + (hash << 6) + (hash << 16) - hash;
}
return hash;
}
/**
* Convert numeric/byte-array seed to a 6+ length string
*
* @param seed The seed value to convert (either a number or array of numbers)
* @returns A string representation of the seed (minimum 6 characters)
*/
export function seedToString(seed) {
if (typeof seed === 'number') {
let hex = seed.toString(16);
if (hex.length < 6) {
hex = hex.padEnd(6, '0');
}
return hex;
}
if (Array.isArray(seed)) {
let hex = seed.map((b) => b.toString(16).padStart(2, '0')).join('');
if (hex.length < 6) {
hex = hex.padEnd(6, '0');
}
return hex;
}
return 'seed000';
}
/**
* Builds a full <svg> string containing the Maskicon shapes.
*
* @param seed The seed value used to generate the icon
* @param size The size of the SVG icon in pixels
* @returns An SVG string representing the Maskicon
*/
export function createMaskiconSVG(seed, size = 100) {
// 1) Convert seed to string, then hash
const str = seedToString(seed);
const hashVal = sdbmHash(str);
// 2) Pick color pair based on the hash
const colorPairIndex = Math.abs(hashVal) % colorPairs.length;
const [bgColor, fgColor] = colorPairs[colorPairIndex];
// 3) Geometry setup
const grid = 2;
const margin = size * 0.25;
const innerSize = size - 2 * margin;
const cellSize = innerSize / grid;
let pathData = '';
const filledGrid = Array.from({ length: grid }, () => Array(grid).fill(false));
const startX = Math.floor(grid / 2);
const startY = Math.floor(grid / 2);
const stack = [[startX, startY]];
filledGrid[startX][startY] = true;
while (stack.length > 0) {
// Using destructuring assignment with a non-null assertion is safe here because we've verified stack.length > 0
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const [x, y] = stack.pop();
// eslint-disable-next-line no-bitwise
const cellHash = Math.abs(hashVal >> (x * 3 + y * 5)) & 15;
const neighbors = [];
const directions = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0],
];
for (const [dx, dy] of directions) {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < grid && ny >= 0 && ny < grid && !filledGrid[nx][ny]) {
neighbors.push([nx, ny]);
}
}
while (neighbors.length > 0) {
const idx = Math.abs(cellHash + neighbors.length) % neighbors.length;
const [nx, ny] = neighbors.splice(idx, 1)[0];
stack.push([nx, ny]);
filledGrid[nx][ny] = true;
}
// Determine shape: square or right triangle (with rotation)
const rotation = (cellHash % 4) * 90;
const isSquare = cellHash % 5 === 0; // ~20% chance
const cx = margin + x * cellSize;
const cy = margin + y * cellSize;
if (isSquare) {
pathData += `M${cx},${cy} h${cellSize} v${cellSize} h-${cellSize}z `;
}
else if (rotation === 0) {
pathData += `M${cx},${cy} h${cellSize} v${cellSize}z `;
}
else if (rotation === 90) {
pathData += `M${cx + cellSize},${cy} v${cellSize} h-${cellSize}z `;
}
else if (rotation === 180) {
pathData += `M${cx + cellSize},${cy + cellSize} h-${cellSize} v-${cellSize}z `;
}
else {
pathData += `M${cx},${cy + cellSize} v-${cellSize} h${cellSize}z `;
}
}
// 4) Construct final SVG string (always rectangular)
let svgString = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">`;
svgString += `<rect width="${size}" height="${size}" fill="${bgColor}" />`;
svgString += `<path d="${pathData}" fill="${fgColor}" />`;
svgString += `</svg>`;
return svgString;
}
const svgCache = {};
/**
* Returns a Promise that resolves to the final <svg> string for the given address.
*
* @param address The address to generate the Maskicon for
* @param size The size of the icon in pixels
* @returns A promise that resolves to an SVG string
*/
export async function getMaskiconSVG(address, size) {
const cacheKey = `${address.toLowerCase()}:${size}`;
if (svgCache[cacheKey]) {
return svgCache[cacheKey];
}
// Extract the account address from CAIP-10 format if needed
const accountAddress = extractAccountAddress(address);
// Generate appropriate seed based on address format
const seed = generateIconSeed(accountAddress);
const svgString = createMaskiconSVG(seed, size);
svgCache[cacheKey] = svgString;
return svgString;
}
//# sourceMappingURL=Maskicon.utilities.mjs.map