Files
Beyond-KLE/mock/src/ui/phy/PhyProperties.vue
T
ewdernaandClaude Opus 5.5 4d1eb23cf8 mock: PHY Editorを作り直す(T-29)
provisional/phy-editor-feedback.md の合意事項と phy-editor-keybindings.md の割り当て案に沿って、PHY 画面を一から書き直した。
App.vue は共通のシェル(入出力・履歴・タブ)だけにし、PHY は src/ui/phy/ に、コンポーネント・composable・純粋モジュール(logic.ts, viewport.ts)を分けて置いた。

- 画面確認で見つかった不具合(読み込み時の全体表示、Shift+クリック、削除の確認の大きさ、Alt+↑、フォーカス中の二重描画、長押し中の選択など)を直し、キャンバス上の実際の操作で確かめる e2e を足した
- 階層パネルでクラスターの子を余分な折りたたみで包まず、件数を配下のキー・部品の数にそろえた
- 実装は Codex(Luna)。検証: vue-tsc 0件、Vitest 64件、Playwright 30件、prettier --check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VbfgHT848s3JbuFw8MKFF8
2026-10-07 20:54:37 +09:00

178 lines
5.3 KiB
Vue

<script setup lang="ts">
import { computed, ref } from "vue";
import type { Node, Transform } from "../../domain/phy";
const props = defineProps<{
selectedCount: number;
selectedId: string | null;
item: Node | null;
mismatch: boolean;
}>();
const emit = defineEmits<{
update: [id: string, change: (node: Node) => void, label?: string];
applyFrame: [id: string, frame: Transform];
setDefault: [groupId: string, variantId: string];
changeVariant: [group: Node, variantId: string];
deleteNode: [id: string];
addVariant: [groupId: string, name: string];
}>();
const group = computed(() => (props.item?.type === "variantGroup" ? props.item : null));
const frameX = ref(0);
const frameY = ref(0);
const frameR = ref(0);
</script>
<template>
<aside class="properties pane">
<fieldset :disabled="mismatch">
<p v-if="selectedCount > 1">{{ selectedCount }}件を選択中</p>
<template v-else-if="item">
<h2>{{ item.type }}</h2>
<label
>X
<input
type="number"
step="any"
:value="item.transform.x"
@change="
emit(
'update',
item.id,
(n) => (n.transform.x = Number(($event.target as HTMLInputElement).value)),
)
"
/></label>
<label
>Y
<input
type="number"
step="any"
:value="item.transform.y"
@change="
emit(
'update',
item.id,
(n) => (n.transform.y = Number(($event.target as HTMLInputElement).value)),
)
"
/></label>
<label
>Rotation
<input
type="number"
step="any"
:value="item.transform.rotation"
@change="
emit(
'update',
item.id,
(n) => (n.transform.rotation = Number(($event.target as HTMLInputElement).value)),
)
"
/></label>
<template v-if="item.type === 'key'">
<label
>Width
<input
type="number"
step="any"
:value="item.size.w"
@change="
emit('update', item.id, (n) => {
if (n.type === 'key') n.size.w = Number(($event.target as HTMLInputElement).value);
})
"
/></label>
<label
>Height
<input
type="number"
step="any"
:value="item.size.h"
@change="
emit('update', item.id, (n) => {
if (n.type === 'key') n.size.h = Number(($event.target as HTMLInputElement).value);
})
"
/></label>
<label
>Label
<input
:value="item.labels?.[0]"
@change="
emit('update', item.id, (n) => {
if (n.type === 'key') n.labels = [($event.target as HTMLInputElement).value];
})
"
/></label>
</template>
<template v-if="item.type === 'cluster'">
<label
>名前
<input
:value="item.name"
@change="
emit('update', item.id, (n) => {
if (n.type === 'cluster') n.name = ($event.target as HTMLInputElement).value;
})
"
/></label>
<h3>座標枠変更</h3>
<label>原点X <input type="number" v-model.number="frameX" /></label>
<label>原点Y <input type="number" v-model.number="frameY" /></label>
<label>角度 <input type="number" v-model.number="frameR" /></label>
<button @click="emit('applyFrame', item.id, { x: frameX, y: frameY, rotation: frameR })">
適用
</button>
</template>
<template v-if="group">
<label
>名前
<input
:value="group.name"
@change="
emit('update', group.id, (n) => {
if (n.type === 'variantGroup') n.name = ($event.target as HTMLInputElement).value;
})
"
/></label>
<div v-for="v in group.children" :key="v.id">
{{ v.name }} {{ v.id === group.default ? "✓" : "" }}
<button @click="emit('setDefault', group.id, v.id)">既定</button>
<button @click="emit('changeVariant', group, v.id)">表示</button>
<button :disabled="group.children.length <= 2" @click="emit('deleteNode', v.id)">削除</button>
</div>
<button @click="emit('addVariant', group.id, 'Option ' + (group.children.length + 1))">追加</button>
</template>
</template>
<p v-else-if="selectedCount === 0">ノードを選択してください</p>
</fieldset>
</aside>
</template>
<style scoped>
.pane {
padding: 10px;
min-height: 0;
overflow: auto;
background: #fff;
border: 1px solid #cbd1da;
border-radius: 6px;
}
.properties fieldset {
border: 0;
padding: 0;
margin: 0;
min-width: 0;
}
.properties label {
display: flex;
gap: 6px;
align-items: center;
margin: 8px 0;
}
.properties input {
width: 110px;
margin-left: auto;
}
</style>