Files
Beyond-KLE/mock/src/ui/idx/IdxImport.vue
T
ewdernaandClaude Opus 5.5 c180642fa8 mock: IDX Editorを作り直す(T-31)
provisional/idx-editor-feedback.md の合意事項と T-31 の仮決定に沿って、IdxPanel を捨てて ui/idx/ に一から書き直した。
並びのリストは置かず、左にバリアントのツリー、中央のキャンバスでドラッグして並べ替え、右にプリセット・取り込み・未登録の登録を置いた。

- order の操作(隠れたキーのくっつき先、挿入、入れ替え、連番の並べ直し)を domain/idxOrder.ts に、
  走査・半身の分割・行の割り当てのずらし・マトリクス順を domain/idxScan.ts に、純粋関数として置いた。行と列の判定は rows.ts をそのまま使う
- QMK keyboard.json・VIA定義・KLE JSON の取り込みは、位置と回転で表示中のキーに照合する(io/layoutImport.ts、domain/idxImport.ts)
- 回転したキーでも出力番号を正立させる
- Codex の設定ファイルが壊れていて起動できなかったため、実装は Claude が直接行った
- 検証: vue-tsc 0件、Vitest 101件(うち新規17件)、Playwright 48件(IDX 8件を書き直し)、prettier --check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GzX13A7HseQz9n6etGbFDg
2026-10-25 10:59:44 +09:00

92 lines
2.8 KiB
Vue

<script setup lang="ts">
// 既存の定義(QMK keyboard.json / VIA定義 / KLE JSON)からの取り込み。照合は親が表示中のキーで行う。T-31。
import { ref } from "vue";
import type { Placement } from "../../domain/idxImport";
import { readLayoutPlacements } from "../../io/layoutImport";
defineProps<{ message: string }>();
const emit = defineEmits<{ apply: [placements: Placement[], source: string]; error: [text: string] }>();
const input = ref<HTMLInputElement>();
const pending = ref<{ name: string; data: unknown; layouts: string[] } | null>(null);
const layout = ref("");
async function read(file: File) {
try {
const data = JSON.parse(await file.text()) as unknown;
const result = readLayoutPlacements(data);
if (result.layouts && !result.layout) {
pending.value = { name: file.name, data, layouts: result.layouts };
layout.value = result.layouts[0]!;
return;
}
pending.value = null;
emit("apply", result.placements, result.layout ? `${file.name} / ${result.layout}` : file.name);
} catch (e) {
emit("error", String(e));
}
}
function applyLayout() {
if (!pending.value) return;
const { placements } = readLayoutPlacements(pending.value.data, layout.value);
emit("apply", placements, `${pending.value.name} / ${layout.value}`);
pending.value = null;
}
</script>
<template>
<section class="import">
<h3>既存の定義から取り込む</h3>
<p class="hint">
QMK keyboard.json・VIA定義・KLE JSON のキー順を、位置が一致する表示中のキーに写します。
</p>
<button @click="input?.click()">ファイルを選ぶ…</button>
<input
ref="input"
hidden
type="file"
accept=".json,application/json"
data-testid="idx-import-file"
@change="
(e) => {
const f = (e.target as HTMLInputElement).files?.[0];
if (f) void read(f);
(e.target as HTMLInputElement).value = '';
}
"
/>
<div v-if="pending" class="layouts">
<label
>レイアウト
<select v-model="layout" aria-label="取り込むレイアウト">
<option v-for="name in pending.layouts" :key="name" :value="name">{{ name }}</option>
</select>
</label>
<button @click="applyLayout">取り込む</button>
<button @click="pending = null">やめる</button>
</div>
<p v-if="message" class="result" data-testid="idx-import-result">{{ message }}</p>
</section>
</template>
<style scoped>
.import {
margin: 0 0 14px;
}
h3 {
font-size: 14px;
margin: 0 0 6px;
}
.hint,
.result {
font-size: 12px;
color: #667;
margin: 4px 0;
}
.result {
color: #20242b;
}
.layouts {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
margin-top: 6px;
}
</style>