Replace history color icon with pattern thumbnail
This commit is contained in:
+3
-3
@@ -128,9 +128,9 @@ input[type="range"] { accent-color: var(--green); }
|
||||
.history-heading p:not(.section-kicker) { margin: 8px 0 0; color: var(--muted); font-size: 12px; }
|
||||
.history-heading .search-box { width: min(330px, 100%); }
|
||||
.history-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding-top: 18px; }
|
||||
.history-card { min-width: 0; padding: 16px; display: grid; grid-template-columns: 58px minmax(0,1fr); gap: 12px; border: 1px solid var(--line); background: var(--card); }
|
||||
.history-swatches { width: 58px; height: 58px; display: flex; overflow: hidden; border: 1px solid var(--ink); }
|
||||
.history-swatches i { min-width: 3px; }
|
||||
.history-card { min-width: 0; padding: 16px; display: grid; grid-template-columns: 86px minmax(0,1fr); gap: 13px; border: 1px solid var(--line); background: var(--card); }
|
||||
.history-thumbnail { width: 86px; height: 86px; display: grid; place-items: center; overflow: hidden; border: 1px solid var(--ink); background-color: #f8f6f0; background-image: linear-gradient(45deg, #dedbd3 25%, transparent 25%), linear-gradient(-45deg, #dedbd3 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #dedbd3 75%), linear-gradient(-45deg, transparent 75%, #dedbd3 75%); background-position: 0 0, 0 5px, 5px -5px, -5px 0; background-size: 10px 10px; }
|
||||
.history-thumbnail canvas { display: block; width: auto; height: auto; max-width: 100%; max-height: 100%; image-rendering: pixelated; }
|
||||
.history-card h3 { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 3px 0 7px; font-size: 14px; }
|
||||
.history-card p { margin: 0; color: var(--muted); font-size: 10px; }
|
||||
.history-actions { grid-column: 1 / -1; display: flex; gap: 7px; justify-content: flex-end; }
|
||||
|
||||
+27
-6
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user