Files
remoteconn-gitea/apps/miniprogram/utils/terminalIcons.js
2026-03-21 18:57:10 +08:00

186 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* global require, module */
const { toSvgDataUri } = require("./svgDataUri");
const { ICON_SVG_SOURCES } = require("./iconSvgSources");
const { tintSvgMarkup } = require("./themedIcons");
const DEFAULT_SHELL_ICON_COLOR = "#9CA9BF";
const DEFAULT_SHELL_ACTIVE_ICON_COLOR = "#5BD2FF";
const DEFAULT_SHELL_CTRL_C_HIGHLIGHT_COLOR = "#5BD2FF";
const CTRL_C_PRIMARY_SOURCE_COLOR = "#FFC16E";
const CTRL_C_HIGHLIGHT_SOURCE_COLOR = "#5BD2FF";
const HEX_COLOR_PATTERN = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
const TERMINAL_ICON_NAMES = Object.freeze([
"backspace",
"cancel",
"clear",
"clear-input",
"codex",
"ctrlc",
"down",
"enter",
"esc",
"home",
"keyboard",
"left",
"paste",
"reading",
"record",
"right",
"sent",
"shift",
"stopreading",
"tab",
"up",
"voice"
]);
const terminalIconCache = Object.create(null);
const terminalActiveIconCache = Object.create(null);
function normalizeShellColor(value, fallback) {
const normalized = String(value || "")
.trim()
.toUpperCase();
return HEX_COLOR_PATTERN.test(normalized) ? normalized : fallback;
}
function hexToRgb(value) {
const normalized = normalizeShellColor(value, "#000000");
return {
r: parseInt(normalized.slice(1, 3), 16),
g: parseInt(normalized.slice(3, 5), 16),
b: parseInt(normalized.slice(5, 7), 16)
};
}
function resolveRelativeLuminance(value) {
const rgb = hexToRgb(value);
const channelToLinear = (channel) => {
const normalized = channel / 255;
if (normalized <= 0.03928) {
return normalized / 12.92;
}
return ((normalized + 0.055) / 1.055) ** 2.4;
};
return channelToLinear(rgb.r) * 0.2126 + channelToLinear(rgb.g) * 0.7152 + channelToLinear(rgb.b) * 0.0722;
}
function contrastRatio(left, right) {
const leftLuminance = resolveRelativeLuminance(left);
const rightLuminance = resolveRelativeLuminance(right);
const lighter = Math.max(leftLuminance, rightLuminance);
const darker = Math.min(leftLuminance, rightLuminance);
return (lighter + 0.05) / (darker + 0.05);
}
function toCamelIconName(name) {
return String(name || "").replace(/-([a-zA-Z0-9])/g, (_, segment) => segment.toUpperCase());
}
function escapeRegExp(value) {
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function replaceSvgHexColor(svg, sourceColor, targetColor) {
const source = normalizeShellColor(sourceColor, sourceColor);
const target = normalizeShellColor(targetColor, targetColor);
return String(svg || "").replace(
new RegExp(`\\b(fill|stroke)="${escapeRegExp(source)}"`, "g"),
(_match, attribute) => `${attribute}="${target}"`
);
}
/**
* `Ctrl+C` 图标本身是双段结构:
* 1. `Ctrl` 保持终端工具色,继续跟随工具区主题;
* 2. `C` 单独保留强调色,避免被统一着色后丢掉原始 SVG 的层次感。
*/
function tintTerminalToolSvgMarkup(name, svg, primaryColor, highlightColor) {
if (name !== "ctrlc") {
return tintSvgMarkup(svg, primaryColor);
}
const primaryTinted = replaceSvgHexColor(svg, CTRL_C_PRIMARY_SOURCE_COLOR, primaryColor);
return replaceSvgHexColor(primaryTinted, CTRL_C_HIGHLIGHT_SOURCE_COLOR, highlightColor);
}
/**
* 终端工具按钮有两组颜色:
* 1. 常态沿用 shell accent和终端工具区其余图标保持一致
* 2. 激活态切到 shell text让 reading 图标在无外层底板时仍然足够醒目。
*/
function buildTerminalToolIconVariantMap(color, highlightColor, cache) {
const cacheKey = `${normalizeShellColor(color, DEFAULT_SHELL_ICON_COLOR)}:${normalizeShellColor(
highlightColor,
DEFAULT_SHELL_CTRL_C_HIGHLIGHT_COLOR
)}`;
if (cache[cacheKey]) {
return cache[cacheKey];
}
const normalizedColor = normalizeShellColor(color, DEFAULT_SHELL_ICON_COLOR);
const normalizedHighlightColor = normalizeShellColor(
highlightColor,
DEFAULT_SHELL_CTRL_C_HIGHLIGHT_COLOR
);
const iconMap = {};
TERMINAL_ICON_NAMES.forEach((name) => {
const svg = ICON_SVG_SOURCES[name];
if (!svg) return;
const dataUri = toSvgDataUri(
tintTerminalToolSvgMarkup(name, svg, normalizedColor, normalizedHighlightColor)
);
iconMap[name] = dataUri;
const camelName = toCamelIconName(name);
if (camelName !== name) {
iconMap[camelName] = dataUri;
}
});
cache[cacheKey] = iconMap;
return iconMap;
}
/**
* reading 激活态所在位置是终端页工具栏,而不是 shell 输出区。
* 因此优先使用更醒目的 UI accent若当前 accent 与页面背景太接近,
* 再退回 shell 文本色,避免浅色模式下“已开启但看不清”。
*/
function resolveTerminalToolActiveColor(settings) {
const source = settings && typeof settings === "object" ? settings : {};
const toolbarBg = normalizeShellColor(source.uiBgColor, "#192B4D");
const accentColor = normalizeShellColor(source.uiAccentColor, DEFAULT_SHELL_ACTIVE_ICON_COLOR);
const shellTextColor = normalizeShellColor(source.shellTextColor, "#E6F0FF");
const accentContrast = contrastRatio(toolbarBg, accentColor);
const shellTextContrast = contrastRatio(toolbarBg, shellTextColor);
if (accentContrast >= 3 || accentContrast >= shellTextContrast * 0.92) {
return accentColor;
}
return shellTextColor;
}
function resolveTerminalToolCtrlCHighlightColor(settings) {
const source = settings && typeof settings === "object" ? settings : {};
return normalizeShellColor(source.uiAccentColor, DEFAULT_SHELL_CTRL_C_HIGHLIGHT_COLOR);
}
function buildTerminalToolIconMap(settings) {
const source = settings && typeof settings === "object" ? settings : {};
return buildTerminalToolIconVariantMap(
normalizeShellColor(source.shellAccentColor, DEFAULT_SHELL_ICON_COLOR),
resolveTerminalToolCtrlCHighlightColor(source),
terminalIconCache
);
}
function buildTerminalToolActiveIconMap(settings) {
return buildTerminalToolIconVariantMap(
resolveTerminalToolActiveColor(settings),
resolveTerminalToolCtrlCHighlightColor(settings),
terminalActiveIconCache
);
}
module.exports = {
buildTerminalToolActiveIconMap,
buildTerminalToolIconMap
};