2024-04-05 18:36:32 +09:00
|
|
|
import { feeLevels, defaultMempoolFeeColors, contrastMempoolFeeColors } from '../../app.constants';
|
2023-12-07 11:12:20 +00:00
|
|
|
import { Color } from './sprite-types';
|
2023-12-13 10:59:28 +00:00
|
|
|
import TxView from './tx-view';
|
2023-12-07 11:12:20 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-09-22 16:49:08 +00:00
|
|
|
export function colorToHex(color: Color): string {
|
|
|
|
return [color.r, color.g, color.b].map(c => Math.round(c * 255).toString(16)).join('');
|
|
|
|
}
|
|
|
|
|
2023-12-07 11:12:20 +00:00
|
|
|
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,
|
2023-12-13 10:59:28 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-09-22 16:49:08 +00:00
|
|
|
export function mix(color1: Color, color2: Color, amount: number): Color {
|
|
|
|
return {
|
|
|
|
r: color1.r * (1 - amount) + color2.r * amount,
|
|
|
|
g: color1.g * (1 - amount) + color2.g * amount,
|
|
|
|
b: color1.b * (1 - amount) + color2.b * amount,
|
|
|
|
a: color1.a * (1 - amount) + color2.a * amount,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-12-13 10:59:28 +00:00
|
|
|
export function setOpacity(color: Color, opacity: number): Color {
|
|
|
|
return {
|
|
|
|
...color,
|
|
|
|
a: opacity
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2024-03-31 03:54:11 +00:00
|
|
|
interface ColorPalette {
|
|
|
|
base: Color[],
|
|
|
|
audit: Color[],
|
|
|
|
marginal: Color[],
|
|
|
|
baseLevel: (tx: TxView, rate: number, time: number) => number,
|
|
|
|
}
|
|
|
|
|
2023-12-13 10:59:28 +00:00
|
|
|
// precomputed colors
|
2024-04-08 15:01:38 +09:00
|
|
|
const defaultColors: { [key: string]: ColorPalette } = {
|
|
|
|
fee: {
|
|
|
|
base: defaultMempoolFeeColors.map(hexToColor),
|
|
|
|
audit: [],
|
|
|
|
marginal: [],
|
|
|
|
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
|
|
|
},
|
|
|
|
}
|
|
|
|
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 };
|
|
|
|
|
2023-12-13 10:59:28 +00:00
|
|
|
export const defaultAuditColors = {
|
|
|
|
censored: hexToColor('f344df'),
|
|
|
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
|
|
|
added: hexToColor('0099ff'),
|
2024-08-02 10:46:07 +00:00
|
|
|
added_prioritized: darken(desaturate(hexToColor('0099ff'), 0.15), 0.85),
|
2024-04-02 02:02:17 +00:00
|
|
|
prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
2024-04-05 18:36:32 +09:00
|
|
|
accelerated: hexToColor('8f5ff6'),
|
|
|
|
};
|
|
|
|
|
2024-04-08 15:01:38 +09:00
|
|
|
const contrastColors: { [key: string]: ColorPalette } = {
|
|
|
|
fee: {
|
|
|
|
base: contrastMempoolFeeColors.map(hexToColor),
|
|
|
|
audit: [],
|
|
|
|
marginal: [],
|
|
|
|
baseLevel: (tx: TxView, rate: number) => feeLevels.findIndex((feeLvl) => Math.max(1, rate) < feeLvl) - 1
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for (const key in contrastColors) {
|
|
|
|
const base = contrastColors[key].base;
|
|
|
|
contrastColors[key].audit = base.map((color) => darken(desaturate(color, 0.3), 0.9));
|
|
|
|
contrastColors[key].marginal = base.map((color) => darken(desaturate(color, 0.8), 1.1));
|
|
|
|
contrastColors['unmatched' + key] = {
|
|
|
|
base: contrastColors[key].base.map(c => setOpacity(c, 0.2)),
|
|
|
|
audit: contrastColors[key].audit.map(c => setOpacity(c, 0.2)),
|
|
|
|
marginal: contrastColors[key].marginal.map(c => setOpacity(c, 0.2)),
|
|
|
|
baseLevel: contrastColors[key].baseLevel,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export { contrastColors as contrastColors };
|
|
|
|
|
2024-04-05 18:36:32 +09:00
|
|
|
export const contrastAuditColors = {
|
|
|
|
censored: hexToColor('ffa8ff'),
|
|
|
|
missing: darken(desaturate(hexToColor('ffa8ff'), 0.3), 0.7),
|
|
|
|
added: hexToColor('00bb98'),
|
2024-08-02 10:46:07 +00:00
|
|
|
added_prioritized: darken(desaturate(hexToColor('00bb98'), 0.15), 0.85),
|
2024-04-05 18:36:32 +09:00
|
|
|
prioritized: darken(desaturate(hexToColor('00bb98'), 0.3), 0.7),
|
|
|
|
accelerated: hexToColor('8f5ff6'),
|
2023-12-13 10:59:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export function defaultColorFunction(
|
|
|
|
tx: TxView,
|
2024-03-31 03:54:11 +00:00
|
|
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = defaultColors.fee,
|
|
|
|
auditColors: { [status: string]: Color } = defaultAuditColors,
|
|
|
|
relativeTime?: number,
|
2023-12-13 10:59:28 +00:00
|
|
|
): Color {
|
|
|
|
const rate = tx.fee / tx.vsize; // color by simple single-tx fee rate
|
2024-04-08 15:01:38 +09:00
|
|
|
const levelIndex = colors.baseLevel(tx, rate, relativeTime || (Date.now() / 1000));
|
|
|
|
const levelColor = colors.base[levelIndex] || colors.base[defaultMempoolFeeColors.length - 1];
|
2023-12-13 10:59:28 +00:00
|
|
|
// Normal mode
|
|
|
|
if (!tx.scene?.highlightingEnabled) {
|
|
|
|
if (tx.acc) {
|
|
|
|
return auditColors.accelerated;
|
|
|
|
} else {
|
2024-03-31 03:54:11 +00:00
|
|
|
return levelColor;
|
2023-12-13 10:59:28 +00:00
|
|
|
}
|
2024-03-31 03:54:11 +00:00
|
|
|
return levelColor;
|
2023-12-13 10:59:28 +00:00
|
|
|
}
|
|
|
|
// Block audit
|
|
|
|
switch(tx.status) {
|
|
|
|
case 'censored':
|
|
|
|
return auditColors.censored;
|
|
|
|
case 'missing':
|
|
|
|
case 'sigop':
|
|
|
|
case 'rbf':
|
2024-04-08 15:01:38 +09:00
|
|
|
return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1];
|
2023-12-13 10:59:28 +00:00
|
|
|
case 'fresh':
|
|
|
|
case 'freshcpfp':
|
|
|
|
return auditColors.missing;
|
|
|
|
case 'added':
|
|
|
|
return auditColors.added;
|
2024-08-02 10:46:07 +00:00
|
|
|
case 'added_prioritized':
|
|
|
|
return auditColors.added_prioritized;
|
2024-04-02 02:02:17 +00:00
|
|
|
case 'prioritized':
|
|
|
|
return auditColors.prioritized;
|
2024-08-17 00:14:33 +00:00
|
|
|
case 'added_deprioritized':
|
|
|
|
return auditColors.added_prioritized;
|
|
|
|
case 'deprioritized':
|
|
|
|
return auditColors.prioritized;
|
2023-12-13 10:59:28 +00:00
|
|
|
case 'selected':
|
2024-04-08 15:01:38 +09:00
|
|
|
return colors.marginal[levelIndex] || colors.marginal[defaultMempoolFeeColors.length - 1];
|
2023-12-13 10:59:28 +00:00
|
|
|
case 'accelerated':
|
|
|
|
return auditColors.accelerated;
|
|
|
|
case 'found':
|
|
|
|
if (tx.context === 'projected') {
|
2024-04-08 15:01:38 +09:00
|
|
|
return colors.audit[levelIndex] || colors.audit[defaultMempoolFeeColors.length - 1];
|
2023-12-13 10:59:28 +00:00
|
|
|
} else {
|
2024-03-31 03:54:11 +00:00
|
|
|
return levelColor;
|
2023-12-13 10:59:28 +00:00
|
|
|
}
|
|
|
|
default:
|
|
|
|
if (tx.acc) {
|
|
|
|
return auditColors.accelerated;
|
|
|
|
} else {
|
2024-03-31 03:54:11 +00:00
|
|
|
return levelColor;
|
2023-12-13 10:59:28 +00:00
|
|
|
}
|
2023-12-07 11:12:20 +00:00
|
|
|
}
|
2024-04-05 18:36:32 +09:00
|
|
|
}
|
|
|
|
|
2024-04-08 15:01:38 +09:00
|
|
|
export function contrastColorFunction(
|
|
|
|
tx: TxView,
|
|
|
|
colors: { base: Color[], audit: Color[], marginal: Color[], baseLevel: (tx: TxView, rate: number, time: number) => number } = contrastColors.fee,
|
|
|
|
auditColors: { [status: string]: Color } = contrastAuditColors,
|
|
|
|
relativeTime?: number,
|
|
|
|
): Color {
|
|
|
|
return defaultColorFunction(tx, colors, auditColors, relativeTime);
|
|
|
|
}
|
|
|
|
|
2024-04-04 06:56:37 +00:00
|
|
|
export function ageColorFunction(
|
|
|
|
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,
|
2024-04-08 15:01:38 +09:00
|
|
|
theme?: string,
|
2024-04-04 06:56:37 +00:00
|
|
|
): Color {
|
2024-04-06 03:44:58 +00:00
|
|
|
if (tx.acc || tx.status === 'accelerated') {
|
|
|
|
return auditColors.accelerated;
|
|
|
|
}
|
|
|
|
|
2024-04-26 16:59:25 +02:00
|
|
|
const color = theme !== 'contrast' && theme !== 'bukele' ? defaultColorFunction(tx, colors, auditColors, relativeTime) : contrastColorFunction(tx, colors, auditColors, relativeTime);
|
2024-04-04 06:56:37 +00:00
|
|
|
|
|
|
|
const ageLevel = (!tx.time ? 0 : (0.8 * Math.tanh((1 / 15) * Math.log2((Math.max(1, 0.6 * ((relativeTime - tx.time) - 60)))))));
|
|
|
|
return {
|
|
|
|
r: color.r,
|
|
|
|
g: color.g,
|
|
|
|
b: color.b,
|
|
|
|
a: color.a * (1 - ageLevel)
|
|
|
|
};
|
2024-04-08 14:54:38 +09:00
|
|
|
}
|