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
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
import type { Ref } from "vue";
|
|
import type { ProjectFile } from "../../domain/project";
|
|
import type { Node } from "../../domain/phy";
|
|
import { longPressColumn, treeIndex, treeNodes } from "./logic";
|
|
|
|
export function usePhyTreeModel(project: () => ProjectFile, focus: Ref<string | null>) {
|
|
function allNodes(nodes = project().phy.root): Node[] {
|
|
return treeNodes(nodes);
|
|
}
|
|
|
|
function node(id: string): Node | undefined {
|
|
return treeIndex(project().phy.root).nodes.get(id);
|
|
}
|
|
|
|
function parentId(id: string): string | null {
|
|
return treeIndex(project().phy.root).selectableParent(id);
|
|
}
|
|
|
|
function isAncestor(candidate: string, id: string): boolean {
|
|
let parent = parentId(id);
|
|
while (parent) {
|
|
if (parent === candidate) return true;
|
|
parent = parentId(parent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function selectedContainer(id: string): string | null {
|
|
return treeIndex(project().phy.root).insertionContainer(id);
|
|
}
|
|
|
|
function groupForVariant(id: string): string | null {
|
|
return treeIndex(project().phy.root).groupForVariant(id);
|
|
}
|
|
|
|
function parentLabel(id: string | null): string {
|
|
if (!id) return "root";
|
|
const item = node(id);
|
|
if (item?.type === "variantGroup") return item.name;
|
|
if (item?.type === "cluster") return item.name || "クラスター";
|
|
const variant = allNodes()
|
|
.flatMap((item) => (item.type === "variantGroup" ? item.children : []))
|
|
.find((item) => item.id === id);
|
|
return variant?.name || id;
|
|
}
|
|
|
|
function pressTargets(id: string): string[] {
|
|
const ancestors: string[] = [];
|
|
let parent = parentId(id);
|
|
while (parent) {
|
|
const group = allNodes().find(
|
|
(item) => item.type === "variantGroup" && item.children.some((variant) => variant.id === parent),
|
|
);
|
|
if (group) {
|
|
ancestors.push(group.id);
|
|
parent = parentId(group.id);
|
|
} else {
|
|
ancestors.push(parent);
|
|
parent = parentId(parent);
|
|
}
|
|
if (ancestors.at(-1) === focus.value) break;
|
|
}
|
|
return longPressColumn(id, ancestors, focus.value || undefined);
|
|
}
|
|
|
|
return {
|
|
allNodes,
|
|
node,
|
|
parentId,
|
|
isAncestor,
|
|
selectedContainer,
|
|
groupForVariant,
|
|
parentLabel,
|
|
pressTargets,
|
|
};
|
|
}
|