From ef4601c555e4fd55d33eaec76291137f8075636f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 16:04:29 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AE=A9=E8=8A=82=E7=82=B9=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E8=90=BD=E5=88=B0=E9=80=89=E4=B8=AD=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=89=80=E5=9C=A8=E7=9A=84=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit src/view/ui-editor/useUiEditorPage.ts 的 deleteNode 默认 treeId 改为 treeForSelectedNode?.src_ui_design ?? activeImageId:选中节点可以来自非激活界面图,Inspector 删除按钮不给 treeId 时会静默删不掉 src/view/ui-editor/uiEditorKeyboardShortcuts.ts 与 view/ui-editor/index.tsx 去掉键盘删除强制传 activeImageId 的接线,treeId 改为可选,同时删掉已无消费者的 activeImageId 入参 tests/uiEditorPage.test.ts 补「非激活树里的节点默认也能删除」用例,uiEditorKeyboardShortcuts.test.ts 断言改为只传节点 id --- .../src/view/ui-editor/index.tsx | 4 +--- .../ui-editor/uiEditorKeyboardShortcuts.ts | 11 ++++----- .../src/view/ui-editor/useUiEditorPage.ts | 15 ++++++++++-- .../tests/uiEditorKeyboardShortcuts.test.ts | 6 +---- .../tests/uiEditorPage.test.ts | 24 +++++++++++++++++++ 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx index b7b49877b..a59a16b3b 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx +++ b/apps/ai-game-creator-shell/src/view/ui-editor/index.tsx @@ -114,21 +114,19 @@ export default function UiEditorPage({ const historyUndo = session.history.undo; const historyRedo = session.history.redo; const selectedNodeId = session.input.selectedNodeId; - const activeImageId = session.input.activeImageId; const deleteNode = session.input.deleteNode; useEffect(() => { const onKeyDown = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId, - activeImageId, deleteNode, historyUndo, historyRedo, }); window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); - }, [activeImageId, deleteNode, historyRedo, historyUndo, selectedNodeId]); + }, [deleteNode, historyRedo, historyUndo, selectedNodeId]); async function save(afterReturn = false) { const result = await session.save.save(); diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/uiEditorKeyboardShortcuts.ts b/apps/ai-game-creator-shell/src/view/ui-editor/uiEditorKeyboardShortcuts.ts index 90657f5b4..38c194e2d 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/uiEditorKeyboardShortcuts.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/uiEditorKeyboardShortcuts.ts @@ -5,8 +5,8 @@ type DeleteResult = { ok: boolean } | undefined; export type UiEditorKeyboardActions = { selectedNodeId: NodeId | null; - activeImageId: UIDesignImageId | null; - deleteNode: (nodeId: NodeId, treeId: UIDesignImageId) => DeleteResult; + /** 省略 treeId 时由调用方按选中节点定位所在 UI 树。 */ + deleteNode: (nodeId: NodeId, treeId?: UIDesignImageId | null) => DeleteResult; historyUndo: () => boolean; historyRedo: () => boolean; }; @@ -50,11 +50,8 @@ export function handleUiEditorKeyDown( !event.shiftKey && !isInteractiveTarget(event.target) ) { - if (actions.selectedNodeId && actions.activeImageId) { - const result = actions.deleteNode( - actions.selectedNodeId, - actions.activeImageId, - ); + if (actions.selectedNodeId) { + const result = actions.deleteNode(actions.selectedNodeId); if (result?.ok) { event.preventDefault(); event.stopPropagation(); diff --git a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts index df7efe9f0..33d4719be 100644 --- a/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts +++ b/apps/ai-game-creator-shell/src/view/ui-editor/useUiEditorPage.ts @@ -875,7 +875,12 @@ export function useUiEditorSession( } const deleteNode = useCallback( - (nodeId: NodeId, treeId = activeImageId) => { + ( + nodeId: NodeId, + // 不给 treeId 时落到选中节点真正所在的树:选中节点可以来自非激活界面图, + // 按 activeImageId 找会静默删不掉。 + treeId = treeForSelectedNode?.src_ui_design ?? activeImageId, + ) => { if (!treeId) return; const tree = editorUiTrees.find( (candidate) => candidate.src_ui_design === treeId, @@ -892,7 +897,13 @@ export function useUiEditorSession( } return result; }, - [activeImageId, editorDeleteNode, editorUiTrees, selectedNodeId], + [ + activeImageId, + editorDeleteNode, + editorUiTrees, + selectedNodeId, + treeForSelectedNode, + ], ); function selectSprite(id: SpriteAssetId) { diff --git a/apps/ai-game-creator-shell/tests/uiEditorKeyboardShortcuts.test.ts b/apps/ai-game-creator-shell/tests/uiEditorKeyboardShortcuts.test.ts index f21c284db..4e7237c6c 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorKeyboardShortcuts.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorKeyboardShortcuts.test.ts @@ -25,7 +25,6 @@ describe('ui editor keyboard shortcuts', () => { const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: null, - activeImageId: null, deleteNode: vi.fn(), historyUndo, historyRedo, @@ -47,7 +46,6 @@ describe('ui editor keyboard shortcuts', () => { const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: null, - activeImageId: null, deleteNode: vi.fn(), historyUndo, historyRedo, @@ -70,7 +68,6 @@ describe('ui editor keyboard shortcuts', () => { const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: 'node-1', - activeImageId: 'page-1', deleteNode, historyUndo: vi.fn(() => true), historyRedo: vi.fn(() => true), @@ -93,7 +90,6 @@ describe('ui editor keyboard shortcuts', () => { const listener = (event: KeyboardEvent) => handleUiEditorKeyDown(event, { selectedNodeId: 'node-1', - activeImageId: 'page-1', deleteNode, historyUndo: vi.fn(() => true), historyRedo: vi.fn(() => true), @@ -103,7 +99,7 @@ describe('ui editor keyboard shortcuts', () => { fireEvent.click(zoomIn); fireEvent.keyDown(window, { key: 'Delete' }); - expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1'); + expect(deleteNode).toHaveBeenCalledWith('node-1'); window.removeEventListener('keydown', listener); }); }); diff --git a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts index cb3e91c02..7f82030a4 100644 --- a/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts +++ b/apps/ai-game-creator-shell/tests/uiEditorPage.test.ts @@ -596,6 +596,30 @@ describe('UiEditorPage', () => { expect(result.current.canvas.hiddenNodeIds.size).toBe(0); }); + it('deletes an inspector node from its own tree without an explicit tree id', async () => { + const { result } = await renderLoadedSession( + stateWithPages(['page-a', 'page-b']), + ); + let otherTreeNodeId: string | undefined; + act(() => { + const inserted = result.current.input.insertNode('page-b-root', 'page-b'); + if (inserted?.ok) otherTreeNodeId = inserted.value; + }); + expect(otherTreeNodeId).toBeTruthy(); + + // 选中非激活界面图里的节点:Inspector 删除按钮不给 treeId,必须落到它所在的树。 + act(() => result.current.input.selectDesignImage('page-a')); + act(() => result.current.input.selectNode(otherTreeNodeId!)); + act(() => result.current.inspector.deleteNode(otherTreeNodeId!)); + + const otherTree = result.current.canvas.uiTrees.find( + (tree) => tree.src_ui_design === 'page-b', + ); + expect(otherTree?.root.children.map((child) => child.id)).not.toContain( + otherTreeNodeId, + ); + }); + it('clears selection when deleting a node removes the selected descendant', async () => { const { result } = await renderLoadedSession(stateWithPages(['page'])); const rootId = 'page-root';