provisional/decisions.md の仮決定(T-1〜T-28)に従い、mock/ に Vue 3 + Vite + TypeScript のモックを置いた。 T-3 の1〜3(回転したクラスター内のドラッグと保存の往復、IDX・MAPとの連携、ELCと出力の対応表)まで。疑似デバイスとの往復(T-24)は未着手。 - KLE JSONの入出力は、KLE本家の serial.js の deserialize の結果を期待値とする fixture(tests/fixtures/kle-oracle.json)で照合した。本家のコードは取り込んでいない(T-7) - 基本キーコードの表は QMK の data/constants/keycodes/keycodes_0.0.1_basic.hjson(1e340cd)から抽出した - 実装は Codex(Luna)、レビューは Codex(Sol)。検証: Vitest 53件、Playwright 20件、vue-tsc、vite build - TypeScript は vue-tsc が 7 系に未対応のため 6 系に固定した - 操作性の改善(ELCの提供者の表示、一括配線とGeneratorのピン設定の引き継ぎ、キー内の文字のはみ出し)は別途行う Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AATqjRyp6rrnwTNdYw8XBT
103 lines
3.6 KiB
TypeScript
103 lines
3.6 KiB
TypeScript
import type { ProjectFile } from "./project";
|
|
import { flattenKeys } from "./geometry";
|
|
import { validateProject, type ValidationIssue } from "./validate";
|
|
export type MockTarget = {
|
|
format: "mock-target";
|
|
version: "0.1.0";
|
|
layout: Array<{ keyId: string; x: number; y: number; rotation: number; w: number; h: number }>;
|
|
idx: string[];
|
|
matrix: { rowPins: string[]; colPins: string[]; rows: number[]; cols: number[] };
|
|
keymaps: Array<{ name: string; keycodes: string[] }>;
|
|
mapping: Array<{ number: number; keyId: string; row: number; col: number; label: string }>;
|
|
};
|
|
export type GeneratorResult = { target?: MockTarget; errors: ValidationIssue[] };
|
|
/** Generates preview output after validation, without modifying the project. T-2/T-19. */
|
|
export function generateMockTarget(p: ProjectFile): GeneratorResult {
|
|
const errors = validateProject(p).filter(
|
|
(x) => x.severity === "blocksOutput" || x.severity === "blocksSave",
|
|
);
|
|
if (!p.output.matrix)
|
|
errors.push({
|
|
severity: "blocksOutput",
|
|
code: "matrix-settings-missing",
|
|
message: "Matrix rowPins and colPins must be configured",
|
|
});
|
|
if (p.output.matrix?.kind && p.output.matrix.kind !== "matrix")
|
|
errors.push({
|
|
severity: "blocksOutput",
|
|
code: "unsupported-scan-mode",
|
|
message: `${p.output.matrix.kind} scan mode is not supported`,
|
|
});
|
|
if (errors.length) return { errors };
|
|
const selected = flattenKeys(p.phy, p.output.variantSelection),
|
|
active = new Set(selected.map((x) => x.key.id)),
|
|
order = p.idx.order.filter((id) => active.has(id)),
|
|
matrix = p.output.matrix!;
|
|
const rowIndex = new Map(matrix.rowPins.map((x, i) => [x, i])),
|
|
colIndex = new Map(matrix.colPins.map((x, i) => [x, i]));
|
|
const wiring = new Map(p.elc.keys.map((x) => [x.key, x]));
|
|
const row: number[] = [],
|
|
col: number[] = [];
|
|
for (const id of order) {
|
|
const w = wiring.get(id);
|
|
if (!w || w.detect !== "contact")
|
|
return {
|
|
errors: [
|
|
{
|
|
severity: "blocksOutput",
|
|
code: "unsupported-detection",
|
|
message: "Only contact matrix wiring is supported",
|
|
ids: [id],
|
|
},
|
|
],
|
|
};
|
|
const a = w.terminals.a,
|
|
b = w.terminals.b;
|
|
let r: number | undefined, c: number | undefined;
|
|
if (rowIndex.has(a) && colIndex.has(b)) {
|
|
r = rowIndex.get(a);
|
|
c = colIndex.get(b);
|
|
} else if (rowIndex.has(b) && colIndex.has(a)) {
|
|
r = rowIndex.get(b);
|
|
c = colIndex.get(a);
|
|
}
|
|
if (r === undefined || c === undefined)
|
|
return {
|
|
errors: [
|
|
{
|
|
severity: "blocksOutput",
|
|
code: "unsupported-matrix-wiring",
|
|
message: "Wiring is not a supported row/column matrix",
|
|
ids: [id],
|
|
},
|
|
],
|
|
};
|
|
row.push(r);
|
|
col.push(c);
|
|
}
|
|
const poses = new Map(selected.map((x) => [x.key.id, x]));
|
|
const keymaps = p.map.layers.map((l) => ({
|
|
name: l.name,
|
|
keycodes: order.map((id) => l.keymap.find((a) => a.keyId === id)!.keycode),
|
|
}));
|
|
const target: MockTarget = {
|
|
format: "mock-target",
|
|
version: "0.1.0",
|
|
layout: order.map((id) => {
|
|
const x = poses.get(id)!;
|
|
return { keyId: id, x: x.x, y: x.y, rotation: x.rotation, w: x.key.size.w, h: x.key.size.h };
|
|
}),
|
|
idx: order,
|
|
matrix: { rowPins: [...matrix.rowPins], colPins: [...matrix.colPins], rows: row, cols: col },
|
|
keymaps,
|
|
mapping: order.map((id, i) => ({
|
|
number: i,
|
|
keyId: id,
|
|
row: row[i]!,
|
|
col: col[i]!,
|
|
label: poses.get(id)!.key.labels?.find(Boolean) ?? "",
|
|
})),
|
|
};
|
|
return { target, errors: [] };
|
|
}
|