import { feeLevels, mempoolAgeColors, mempoolFeeColors } from '../../app.constants'; import { Color } from './sprite-types'; import TxView from './tx-view'; export function hexToColor(hex: string): Color { return { r: parseInt(hex.slice(0, 2), 16) / 255, g: parseInt(hex.slice(2, 4), 16) / 255, b: parseInt(hex.slice(4, 6), 16) / 255, a: hex.length > 6 ? parseInt(hex.slice(6, 8), 16) / 255 : 1 }; } export function desaturate(color: Color, amount: number): Color { const gray = (color.r + color.g + color.b) / 6; return { r: color.r + ((gray - color.r) * amount), g: color.g + ((gray - color.g) * amount), b: color.b + ((gray - color.b) * amount), a: color.a, }; } export function darken(color: Color, amount: number): Color { return { r: color.r * amount, g: color.g * amount, b: color.b * amount, a: color.a, }; } export function setOpacity(color: Color, opacity: number): Color { return { ...color, a: opacity }; } interface ColorPalette { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number, } // precomputed colors const defaultColors: { [key: string]: ColorPalette } = { fee: { base: mempoolFeeColors.map(hexToColor), audit: [], marginal: [], baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1 }, age: { base: mempoolAgeColors.map(hexToColor), audit: [], marginal: [], baseLevel: (tx: TxView, rate: number, relativeTime: number) => (!tx.time ? 0 : Math.max(0, Math.round(1.25 * Math.log2((Math.max(1, relativeTime - tx.time)))))) }, } for (const key in defaultColors) { const base = defaultColors[key].base; defaultColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9)); defaultColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1)); defaultColors['unmatched' + key] = { base: defaultColors[key].base.map(c => setOpacity(c, 0.2)), audit: defaultColors[key].audit.map(c => setOpacity(c, 0.2)), marginal: defaultColors[key].marginal.map(c => setOpacity(c, 0.2)), baseLevel: defaultColors[key].baseLevel, }; } export { defaultColors as defaultColors }; export const defaultAuditColors = { censored: hexToColor('f344df'), missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), added: hexToColor('0099ff'), prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), accelerated: hexToColor('8F5FF6'), }; export function defaultColorFunction( tx: TxView, colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee, auditColors: { [status: string]: Color } = defaultAuditColors, relativeTime?: number, ): Color { const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000)); const levelColor = colors.base[levelIndex] || colors.base[mempoolFeeColors.length - 1]; // Normal mode if (!tx.scene?.highlightingEnabled) { if (tx.acc) { return auditColors.accelerated; } else { return levelColor; } return levelColor; } // Block audit switch(tx.status) { case 'censored': return auditColors.censored; case 'missing': case 'sigop': case 'rbf': return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; case 'fresh': case 'freshcpfp': return auditColors.missing; case 'added': return auditColors.added; case 'prioritized': return auditColors.prioritized; case 'selected': return colors.marginal[levelIndex] || colors.marginal[mempoolFeeColors.length - 1]; case 'accelerated': return auditColors.accelerated; case 'found': if (tx.context === 'projected') { return colors.audit[levelIndex] || colors.audit[mempoolFeeColors.length - 1]; } else { return levelColor; } default: if (tx.acc) { return auditColors.accelerated; } else { return levelColor; } } }