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
This commit is contained in:
@@ -69,6 +69,56 @@ export function addComponent(f: PhyFile, at: Transform, partGroup?: string, gen?
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
/** 指定コンテナへキーを追加する。 */
|
||||
export function addKeyToParent(
|
||||
f: PhyFile,
|
||||
parentId: string | null,
|
||||
at: Transform,
|
||||
size: { w: number; h: number },
|
||||
profile = f.profiles[0]!.id,
|
||||
gen?: () => string,
|
||||
): PhyFile {
|
||||
const o = mutable(f),
|
||||
target = parentNodes(o, parentId);
|
||||
target.push({ type: "key", id: nodeId("key", gen), profile, transform: { ...at }, size: { ...size } });
|
||||
return o;
|
||||
}
|
||||
|
||||
/** 指定コンテナへ部品を追加する。 */
|
||||
export function addComponentToParent(
|
||||
f: PhyFile,
|
||||
parentId: string | null,
|
||||
at: Transform,
|
||||
partGroup?: string,
|
||||
gen?: () => string,
|
||||
): PhyFile {
|
||||
const o = mutable(f),
|
||||
target = parentNodes(o, parentId);
|
||||
target.push({
|
||||
type: "component",
|
||||
id: nodeId("component", gen),
|
||||
transform: { ...at },
|
||||
...(partGroup ? { partGroup } : {}),
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
/** root / cluster / variant の指定先にノード群を挿入する。 */
|
||||
export function insertNodes(f: PhyFile, parentId: string | null, nodes: Node[], index?: number): PhyFile {
|
||||
const o = mutable(f),
|
||||
target = parentNodes(o, parentId);
|
||||
target.splice(index ?? target.length, 0, ...structuredClone(nodes));
|
||||
return o;
|
||||
}
|
||||
|
||||
function parentNodes(f: PhyFile, parentId: string | null): Node[] {
|
||||
if (!parentId) return f.root;
|
||||
const n = find(f, parentId);
|
||||
if (n.type === "cluster") return n.children;
|
||||
if (n.type === "variantGroup") throw new Error("Choose a variant as the parent");
|
||||
return findVariant(f, parentId).children;
|
||||
}
|
||||
/** ローカル座標の差分でノードを移動する。 */
|
||||
export function moveNode(f: PhyFile, id: string, dx: number, dy: number): PhyFile {
|
||||
const o = mutable(f),
|
||||
@@ -407,20 +457,38 @@ export function deleteNode(f: PhyFile, id: string): PhyFile {
|
||||
export function duplicateNode(f: PhyFile, id: string, gen?: () => string): PhyFile {
|
||||
const o = mutable(f),
|
||||
n = find(o, id),
|
||||
copy = structuredClone(n);
|
||||
const renew = (x: Node) => {
|
||||
x.id = nodeId(x.type, gen);
|
||||
if (x.type === "cluster") x.children.forEach(renew);
|
||||
if (x.type === "variantGroup")
|
||||
for (const v of x.children) {
|
||||
v.id = nodeId("variant", gen);
|
||||
v.children.forEach(renew);
|
||||
}
|
||||
};
|
||||
renew(copy);
|
||||
copy = cloneNodesWithNewIds([n], gen)[0]!;
|
||||
copy.transform.x += 0.25;
|
||||
copy.transform.y += 0.25;
|
||||
const c = containers(o).get(id)!;
|
||||
c.nodes.splice(c.nodes.indexOf(n) + 1, 0, copy);
|
||||
return o;
|
||||
}
|
||||
/** ノード群を複製し、配下のIDと閉じたdefault参照を振り直す。T-11。 */
|
||||
export function cloneNodesWithNewIds(nodes: Node[], gen?: () => string): Node[] {
|
||||
const copies = structuredClone(nodes);
|
||||
const remap = new Map<string, string>();
|
||||
const renew = (x: Node) => {
|
||||
const oldId = x.id;
|
||||
x.id = nodeId(x.type, gen);
|
||||
remap.set(oldId, x.id);
|
||||
if (x.type === "cluster") x.children.forEach(renew);
|
||||
if (x.type === "variantGroup")
|
||||
for (const v of x.children) {
|
||||
const oldVariantId = v.id;
|
||||
v.id = nodeId("variant", gen);
|
||||
remap.set(oldVariantId, v.id);
|
||||
v.children.forEach(renew);
|
||||
}
|
||||
};
|
||||
copies.forEach(renew);
|
||||
const remapDefaults = (x: Node) => {
|
||||
if (x.type === "cluster") x.children.forEach(remapDefaults);
|
||||
if (x.type === "variantGroup") {
|
||||
x.default = remap.get(x.default) ?? x.default;
|
||||
x.children.forEach((v) => v.children.forEach(remapDefaults));
|
||||
}
|
||||
};
|
||||
copies.forEach(remapDefaults);
|
||||
return copies;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user