first commit
This commit is contained in:
185
apps/miniprogram/utils/terminalIcons.js
Normal file
185
apps/miniprogram/utils/terminalIcons.js
Normal file
@@ -0,0 +1,185 @@
|
||||
/* 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
|
||||
};
|
||||
Reference in New Issue
Block a user