Replace history color icon with pattern thumbnail

This commit is contained in:
wuyanwanwu
2026-08-14 21:56:14 +08:00
parent 6f101661c7
commit 785a089518
14 changed files with 46 additions and 25 deletions
+27 -6
View File
@@ -184,6 +184,7 @@ const PALETTE: BeadColor[] = MARD_221.map(([code, hex]) => {
const rgb: [number, number, number] = [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)];
return { code, name: `MARD ${MARD_SERIES_NAMES[code[0]]}`, hex, rgb, lab: rgbToOklab(rgb) };
});
const PALETTE_BY_CODE = new Map(PALETTE.map((color) => [color.code, color]));
const hexToRgb = (hex: string): [number, number, number] => [
parseInt(hex.slice(1, 3), 16),
@@ -191,6 +192,29 @@ const hexToRgb = (hex: string): [number, number, number] => [
parseInt(hex.slice(5, 7), 16),
];
function HistoryThumbnail({ entry }: { entry: SavedPattern }) {
const thumbnailRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const canvas = thumbnailRef.current;
if (!canvas) return;
canvas.width = Math.max(1, entry.width);
canvas.height = Math.max(1, entry.height);
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
entry.codes.forEach((code, index) => {
if (!code) return;
const color = PALETTE_BY_CODE.get(code);
if (!color) return;
ctx.fillStyle = color.hex;
ctx.fillRect(index % entry.width, Math.floor(index / entry.width), 1, 1);
});
}, [entry]);
return <div className="history-thumbnail" aria-label={`${entry.name} 的完整格子图缩略图`}>
<canvas ref={thumbnailRef} />
</div>;
}
function nearestColor(lab: Oklab, palette: BeadColor[]) {
let closest = palette[0];
let smallest = Number.POSITIVE_INFINITY;
@@ -1455,7 +1479,7 @@ export default function Home() {
if (color) {
ctx.fillStyle = color.hex;
ctx.fillRect(x, y, cell, cell);
const label = `${rowNumberToLetters(row + 1)}${column + 1}`;
const label = color.code;
const [red, green, blue] = hexToRgb(color.hex);
ctx.fillStyle = red * 0.299 + green * 0.587 + blue * 0.114 > 160 ? "#18211d" : "#ffffff";
let fontSize = Math.max(6, Math.floor(cell * 0.29));
@@ -1532,7 +1556,7 @@ export default function Home() {
link.href = url;
link.click();
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
setStatus("PNG 已生成:格内为行字母+列数字,底部为色块和 MARD 色号");
setStatus("PNG 已生成:格内为 MARD 色号,底部为色块和对应色号");
logUsage({ event: "download_png", width: gridWidth, height: gridHeight, colors: usedColors.length });
}, "image/png");
};
@@ -1715,11 +1739,8 @@ export default function Home() {
<div className="search-box"><span></span><input value={historyQuery} onChange={(e) => setHistoryQuery(e.target.value)} placeholder="按名称或 32x32 查询" /></div>
</div>
{filteredHistory.length ? <div className="history-grid">{filteredHistory.map((entry) => {
const colorCounts = new Map<string, number>();
entry.codes.forEach((code) => { if (code) colorCounts.set(code, (colorCounts.get(code) ?? 0) + 1); });
const topCodes = [...colorCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
return <article className="history-card" key={entry.id}>
<div className="history-swatches">{topCodes.map(([code, count]) => <i key={code} style={{ backgroundColor: PALETTE.find((color) => color.code === code)?.hex, flexGrow: count }} />)}</div>
<HistoryThumbnail entry={entry} />
<div><h3>{entry.name}</h3><p>{entry.width} × {entry.height} · {new Date(entry.savedAt).toLocaleString("zh-CN", { hour12: false })}</p></div>
<div className="history-actions"><button onClick={() => restorePattern(entry)}></button><button onClick={() => removePattern(entry.id)}></button></div>
</article>;